diff --git a/CMakeLists.txt b/CMakeLists.txt index 259342aa..e69cd7bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,7 +143,6 @@ set(source_files #All SeriousProton's objects to compile src/audio/sound.cpp src/audio/music.cpp src/clipboard.cpp - src/collisionable.cpp src/engine.cpp src/event.cpp src/graphics/font.cpp @@ -171,9 +170,12 @@ set(source_files #All SeriousProton's objects to compile src/random.cpp src/Renderable.cpp src/resources.cpp - src/scriptInterface.cpp - src/scriptInterfaceMagic.cpp - src/scriptInterfaceSandbox.cpp + src/script/environment.cpp + src/script/environment.h + src/script/conversion.h + src/script/component.h + src/script/callback.h + src/script/callback.cpp src/shaderManager.cpp src/soundManager.cpp src/stringImproved.cpp @@ -201,7 +203,6 @@ set(source_files #All SeriousProton's objects to compile src/audio/sound.h src/audio/music.h src/clipboard.h - src/collisionable.h src/dynamicLibrary.h src/engine.h src/event.h @@ -251,9 +252,6 @@ set(source_files #All SeriousProton's objects to compile src/rect.h src/Renderable.h src/resources.h - src/scriptInterface.h - src/scriptInterfaceMagic.h - src/scriptInterfaceSandbox.h src/shaderManager.h src/soundManager.h src/stringImproved.h @@ -266,6 +264,26 @@ set(source_files #All SeriousProton's objects to compile src/vectorUtils.h src/windowManager.h + src/container/sparseset.h + src/container/chunkedvector.h + src/container/bitset.h + + src/ecs/entity.h + src/ecs/entity.cpp + src/ecs/component.h + src/ecs/component.cpp + src/ecs/query.h + src/ecs/multiplayer.h + src/ecs/system.h + + src/components/collision.h + src/components/multiplayer.h + src/systems/collision.h + src/systems/collision.cpp + + src/multiplayer/collision.h + src/multiplayer/collision.cpp + cmake/glDebug.inl.in "${glDebug_inl}" ) diff --git a/libs/lua/CMakeLists.txt b/libs/lua/CMakeLists.txt index 37842690..fd46e95d 100644 --- a/libs/lua/CMakeLists.txt +++ b/libs/lua/CMakeLists.txt @@ -16,6 +16,7 @@ set(source_files ldump.c lfunc.c lgc.c + linit.c llex.c lmathlib.c lmem.c @@ -40,6 +41,7 @@ add_library(lua STATIC ${source_files}) target_include_directories(lua PUBLIC $) +target_compile_definitions(lua PRIVATE LUA_COMPAT_MATHLIB) #--------------------------------Installation---------------------------------- install( diff --git a/libs/lua/lapi.c b/libs/lua/lapi.c index fea9eb27..332e97d1 100644 --- a/libs/lua/lapi.c +++ b/libs/lua/lapi.c @@ -1,5 +1,5 @@ /* -** $Id: lapi.c,v 2.249 2015/04/06 12:23:48 roberto Exp $ +** $Id: lapi.c $ ** Lua API ** See Copyright Notice in lua.h */ @@ -10,6 +10,7 @@ #include "lprefix.h" +#include #include #include @@ -36,11 +37,14 @@ const char lua_ident[] = "$LuaAuthors: " LUA_AUTHORS " $"; -/* value at a non-valid index */ -#define NONVALIDVALUE cast(TValue *, luaO_nilobject) -/* corresponding test */ -#define isvalid(o) ((o) != luaO_nilobject) +/* +** Test for a valid index (one that is not the 'nilvalue'). +** '!ttisnil(o)' implies 'o != &G(L)->nilvalue', so it is not needed. +** However, it covers the most common cases in a faster way. +*/ +#define isvalid(L, o) (!ttisnil(o) || o != &G(L)->nilvalue) + /* test for pseudo index */ #define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) @@ -48,68 +52,74 @@ const char lua_ident[] = /* test for upvalue */ #define isupvalue(i) ((i) < LUA_REGISTRYINDEX) -/* test for valid but not pseudo index */ -#define isstackindex(i, o) (isvalid(o) && !ispseudo(i)) - -#define api_checkvalidindex(l,o) api_check(l, isvalid(o), "invalid index") -#define api_checkstackindex(l, i, o) \ - api_check(l, isstackindex(i, o), "index not in the stack") - - -static TValue *index2addr (lua_State *L, int idx) { +/* +** Convert an acceptable index to a pointer to its respective value. +** Non-valid indices return the special nil value 'G(L)->nilvalue'. +*/ +static TValue *index2value (lua_State *L, int idx) { CallInfo *ci = L->ci; if (idx > 0) { - TValue *o = ci->func + idx; - api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index"); - if (o >= L->top) return NONVALIDVALUE; - else return o; + StkId o = ci->func.p + idx; + api_check(L, idx <= ci->top.p - (ci->func.p + 1), "unacceptable index"); + if (o >= L->top.p) return &G(L)->nilvalue; + else return s2v(o); } else if (!ispseudo(idx)) { /* negative index */ - api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); - return L->top + idx; + api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1), + "invalid index"); + return s2v(L->top.p + idx); } else if (idx == LUA_REGISTRYINDEX) return &G(L)->l_registry; else { /* upvalues */ idx = LUA_REGISTRYINDEX - idx; api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large"); - if (ttislcf(ci->func)) /* light C function? */ - return NONVALIDVALUE; /* it has no upvalues */ - else { - CClosure *func = clCvalue(ci->func); - return (idx <= func->nupvalues) ? &func->upvalue[idx-1] : NONVALIDVALUE; + if (ttisCclosure(s2v(ci->func.p))) { /* C closure? */ + CClosure *func = clCvalue(s2v(ci->func.p)); + return (idx <= func->nupvalues) ? &func->upvalue[idx-1] + : &G(L)->nilvalue; + } + else { /* light C function or Lua function (through a hook)?) */ + api_check(L, ttislcf(s2v(ci->func.p)), "caller not a C function"); + return &G(L)->nilvalue; /* no upvalues */ } } } + /* -** to be called by 'lua_checkstack' in protected mode, to grow stack -** capturing memory errors +** Convert a valid actual index (not a pseudo-index) to its address. */ -static void growstack (lua_State *L, void *ud) { - int size = *(int *)ud; - luaD_growstack(L, size); +l_sinline StkId index2stack (lua_State *L, int idx) { + CallInfo *ci = L->ci; + if (idx > 0) { + StkId o = ci->func.p + idx; + api_check(L, o < L->top.p, "invalid index"); + return o; + } + else { /* non-positive index */ + api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1), + "invalid index"); + api_check(L, !ispseudo(idx), "invalid index"); + return L->top.p + idx; + } } LUA_API int lua_checkstack (lua_State *L, int n) { int res; - CallInfo *ci = L->ci; + CallInfo *ci; lua_lock(L); + ci = L->ci; api_check(L, n >= 0, "negative 'n'"); - if (L->stack_last - L->top > n) /* stack large enough? */ + if (L->stack_last.p - L->top.p > n) /* stack large enough? */ res = 1; /* yes; check is OK */ - else { /* no; need to grow stack */ - int inuse = cast_int(L->top - L->stack) + EXTRA_STACK; - if (inuse > LUAI_MAXSTACK - n) /* can grow without overflow? */ - res = 0; /* no */ - else /* try to grow stack */ - res = (luaD_rawrunprotected(L, &growstack, &n) == LUA_OK); - } - if (res && ci->top < L->top + n) - ci->top = L->top + n; /* adjust frame top */ + else /* need to grow stack */ + res = luaD_growstack(L, n, 0); + if (res && ci->top.p < L->top.p + n) + ci->top.p = L->top.p + n; /* adjust frame top */ lua_unlock(L); return res; } @@ -121,11 +131,11 @@ LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { lua_lock(to); api_checknelems(from, n); api_check(from, G(from) == G(to), "moving among independent states"); - api_check(from, to->ci->top - to->top >= n, "not enough elements to move"); - from->top -= n; + api_check(from, to->ci->top.p - to->top.p >= n, "stack overflow"); + from->top.p -= n; for (i = 0; i < n; i++) { - setobj2s(to, to->top, from->top + i); - api_incr_top(to); + setobjs2s(to, to->top.p, from->top.p + i); + to->top.p++; /* stack already checked by previous 'api_check' */ } lua_unlock(to); } @@ -141,10 +151,9 @@ LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) { } -LUA_API const lua_Number *lua_version (lua_State *L) { - static const lua_Number version = LUA_VERSION_NUM; - if (L == NULL) return &version; - else return G(L)->version; +LUA_API lua_Number lua_version (lua_State *L) { + UNUSED(L); + return LUA_VERSION_NUM; } @@ -160,28 +169,51 @@ LUA_API const lua_Number *lua_version (lua_State *L) { LUA_API int lua_absindex (lua_State *L, int idx) { return (idx > 0 || ispseudo(idx)) ? idx - : cast_int(L->top - L->ci->func) + idx; + : cast_int(L->top.p - L->ci->func.p) + idx; } LUA_API int lua_gettop (lua_State *L) { - return cast_int(L->top - (L->ci->func + 1)); + return cast_int(L->top.p - (L->ci->func.p + 1)); } LUA_API void lua_settop (lua_State *L, int idx) { - StkId func = L->ci->func; + CallInfo *ci; + StkId func, newtop; + ptrdiff_t diff; /* difference for new top */ lua_lock(L); + ci = L->ci; + func = ci->func.p; if (idx >= 0) { - api_check(L, idx <= L->stack_last - (func + 1), "new top too large"); - while (L->top < (func + 1) + idx) - setnilvalue(L->top++); - L->top = (func + 1) + idx; + api_check(L, idx <= ci->top.p - (func + 1), "new top too large"); + diff = ((func + 1) + idx) - L->top.p; + for (; diff > 0; diff--) + setnilvalue(s2v(L->top.p++)); /* clear new slots */ } else { - api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top"); - L->top += idx+1; /* 'subtract' index (index is negative) */ + api_check(L, -(idx+1) <= (L->top.p - (func + 1)), "invalid new top"); + diff = idx + 1; /* will "subtract" index (as it is negative) */ + } + api_check(L, L->tbclist.p < L->top.p, "previous pop of an unclosed slot"); + newtop = L->top.p + diff; + if (diff < 0 && L->tbclist.p >= newtop) { + lua_assert(hastocloseCfunc(ci->nresults)); + newtop = luaF_close(L, newtop, CLOSEKTOP, 0); } + L->top.p = newtop; /* correct top only after closing any upvalue */ + lua_unlock(L); +} + + +LUA_API void lua_closeslot (lua_State *L, int idx) { + StkId level; + lua_lock(L); + level = index2stack(L, idx); + api_check(L, hastocloseCfunc(L->ci->nresults) && L->tbclist.p == level, + "no variable to close at given level"); + level = luaF_close(L, level, CLOSEKTOP, 0); + setnilvalue(s2v(level)); lua_unlock(L); } @@ -189,11 +221,13 @@ LUA_API void lua_settop (lua_State *L, int idx) { /* ** Reverse the stack segment from 'from' to 'to' ** (auxiliary to 'lua_rotate') +** Note that we move(copy) only the value inside the stack. +** (We do not move additional fields that may exist.) */ -static void reverse (lua_State *L, StkId from, StkId to) { +l_sinline void reverse (lua_State *L, StkId from, StkId to) { for (; from < to; from++, to--) { TValue temp; - setobj(L, &temp, from); + setobj(L, &temp, s2v(from)); setobjs2s(L, from, to); setobj2s(L, to, &temp); } @@ -207,9 +241,8 @@ static void reverse (lua_State *L, StkId from, StkId to) { LUA_API void lua_rotate (lua_State *L, int idx, int n) { StkId p, t, m; lua_lock(L); - t = L->top - 1; /* end of stack segment being rotated */ - p = index2addr(L, idx); /* start of segment */ - api_checkstackindex(L, idx, p); + t = L->top.p - 1; /* end of stack segment being rotated */ + p = index2stack(L, idx); /* start of segment */ api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'"); m = (n >= 0 ? t - n : p - n - 1); /* end of prefix */ reverse(L, p, m); /* reverse the prefix with length 'n' */ @@ -222,12 +255,12 @@ LUA_API void lua_rotate (lua_State *L, int idx, int n) { LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { TValue *fr, *to; lua_lock(L); - fr = index2addr(L, fromidx); - to = index2addr(L, toidx); - api_checkvalidindex(L, to); + fr = index2value(L, fromidx); + to = index2value(L, toidx); + api_check(L, isvalid(L, to), "invalid index"); setobj(L, to, fr); if (isupvalue(toidx)) /* function upvalue? */ - luaC_barrier(L, clCvalue(L->ci->func), fr); + luaC_barrier(L, clCvalue(s2v(L->ci->func.p)), fr); /* LUA_REGISTRYINDEX does not need gc barrier (collector revisits it before finishing collection) */ lua_unlock(L); @@ -236,7 +269,7 @@ LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { LUA_API void lua_pushvalue (lua_State *L, int idx) { lua_lock(L); - setobj2s(L, L->top, index2addr(L, idx)); + setobj2s(L, L->top.p, index2value(L, idx)); api_incr_top(L); lua_unlock(L); } @@ -249,53 +282,53 @@ LUA_API void lua_pushvalue (lua_State *L, int idx) { LUA_API int lua_type (lua_State *L, int idx) { - StkId o = index2addr(L, idx); - return (isvalid(o) ? ttnov(o) : LUA_TNONE); + const TValue *o = index2value(L, idx); + return (isvalid(L, o) ? ttype(o) : LUA_TNONE); } LUA_API const char *lua_typename (lua_State *L, int t) { UNUSED(L); - api_check(L, LUA_TNONE <= t && t < LUA_NUMTAGS, "invalid tag"); + api_check(L, LUA_TNONE <= t && t < LUA_NUMTYPES, "invalid type"); return ttypename(t); } LUA_API int lua_iscfunction (lua_State *L, int idx) { - StkId o = index2addr(L, idx); + const TValue *o = index2value(L, idx); return (ttislcf(o) || (ttisCclosure(o))); } LUA_API int lua_isinteger (lua_State *L, int idx) { - StkId o = index2addr(L, idx); + const TValue *o = index2value(L, idx); return ttisinteger(o); } LUA_API int lua_isnumber (lua_State *L, int idx) { lua_Number n; - const TValue *o = index2addr(L, idx); + const TValue *o = index2value(L, idx); return tonumber(o, &n); } LUA_API int lua_isstring (lua_State *L, int idx) { - const TValue *o = index2addr(L, idx); + const TValue *o = index2value(L, idx); return (ttisstring(o) || cvt2str(o)); } LUA_API int lua_isuserdata (lua_State *L, int idx) { - const TValue *o = index2addr(L, idx); + const TValue *o = index2value(L, idx); return (ttisfulluserdata(o) || ttislightuserdata(o)); } LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { - StkId o1 = index2addr(L, index1); - StkId o2 = index2addr(L, index2); - return (isvalid(o1) && isvalid(o2)) ? luaV_rawequalobj(o1, o2) : 0; + const TValue *o1 = index2value(L, index1); + const TValue *o2 = index2value(L, index2); + return (isvalid(L, o1) && isvalid(L, o2)) ? luaV_rawequalobj(o1, o2) : 0; } @@ -305,23 +338,24 @@ LUA_API void lua_arith (lua_State *L, int op) { api_checknelems(L, 2); /* all other operations expect two operands */ else { /* for unary operations, add fake 2nd operand */ api_checknelems(L, 1); - setobjs2s(L, L->top, L->top - 1); + setobjs2s(L, L->top.p, L->top.p - 1); api_incr_top(L); } /* first operand at top - 2, second at top - 1; result go to top - 2 */ - luaO_arith(L, op, L->top - 2, L->top - 1, L->top - 2); - L->top--; /* remove second operand */ + luaO_arith(L, op, s2v(L->top.p - 2), s2v(L->top.p - 1), L->top.p - 2); + L->top.p--; /* remove second operand */ lua_unlock(L); } LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { - StkId o1, o2; + const TValue *o1; + const TValue *o2; int i = 0; lua_lock(L); /* may call tag method */ - o1 = index2addr(L, index1); - o2 = index2addr(L, index2); - if (isvalid(o1) && isvalid(o2)) { + o1 = index2value(L, index1); + o2 = index2value(L, index2); + if (isvalid(L, o1) && isvalid(L, o2)) { switch (op) { case LUA_OPEQ: i = luaV_equalobj(L, o1, o2); break; case LUA_OPLT: i = luaV_lessthan(L, o1, o2); break; @@ -335,7 +369,7 @@ LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { LUA_API size_t lua_stringtonumber (lua_State *L, const char *s) { - size_t sz = luaO_str2num(s, L->top); + size_t sz = luaO_str2num(s, s2v(L->top.p)); if (sz != 0) api_incr_top(L); return sz; @@ -343,66 +377,66 @@ LUA_API size_t lua_stringtonumber (lua_State *L, const char *s) { LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum) { - lua_Number n; - const TValue *o = index2addr(L, idx); + lua_Number n = 0; + const TValue *o = index2value(L, idx); int isnum = tonumber(o, &n); - if (!isnum) - n = 0; /* call to 'tonumber' may change 'n' even if it fails */ - if (pisnum) *pisnum = isnum; + if (pisnum) + *pisnum = isnum; return n; } LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum) { - lua_Integer res; - const TValue *o = index2addr(L, idx); + lua_Integer res = 0; + const TValue *o = index2value(L, idx); int isnum = tointeger(o, &res); - if (!isnum) - res = 0; /* call to 'tointeger' may change 'n' even if it fails */ - if (pisnum) *pisnum = isnum; + if (pisnum) + *pisnum = isnum; return res; } LUA_API int lua_toboolean (lua_State *L, int idx) { - const TValue *o = index2addr(L, idx); + const TValue *o = index2value(L, idx); return !l_isfalse(o); } LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { - StkId o = index2addr(L, idx); + TValue *o; + lua_lock(L); + o = index2value(L, idx); if (!ttisstring(o)) { if (!cvt2str(o)) { /* not convertible? */ if (len != NULL) *len = 0; + lua_unlock(L); return NULL; } - lua_lock(L); /* 'luaO_tostring' may create a new string */ - luaC_checkGC(L); - o = index2addr(L, idx); /* previous call may reallocate the stack */ luaO_tostring(L, o); - lua_unlock(L); + luaC_checkGC(L); + o = index2value(L, idx); /* previous call may reallocate the stack */ } if (len != NULL) - *len = vslen(o); - return svalue(o); + *len = tsslen(tsvalue(o)); + lua_unlock(L); + return getstr(tsvalue(o)); } -LUA_API size_t lua_rawlen (lua_State *L, int idx) { - StkId o = index2addr(L, idx); - switch (ttype(o)) { - case LUA_TSHRSTR: return tsvalue(o)->shrlen; - case LUA_TLNGSTR: return tsvalue(o)->u.lnglen; - case LUA_TUSERDATA: return uvalue(o)->len; - case LUA_TTABLE: return luaH_getn(hvalue(o)); +LUA_API lua_Unsigned lua_rawlen (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + switch (ttypetag(o)) { + case LUA_VSHRSTR: return tsvalue(o)->shrlen; + case LUA_VLNGSTR: return tsvalue(o)->u.lnglen; + case LUA_VUSERDATA: return uvalue(o)->len; + case LUA_VTABLE: return luaH_getn(hvalue(o)); default: return 0; } } LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { - StkId o = index2addr(L, idx); + const TValue *o = index2value(L, idx); if (ttislcf(o)) return fvalue(o); else if (ttisCclosure(o)) return clCvalue(o)->f; @@ -410,9 +444,8 @@ LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { } -LUA_API void *lua_touserdata (lua_State *L, int idx) { - StkId o = index2addr(L, idx); - switch (ttnov(o)) { +l_sinline void *touserdata (const TValue *o) { + switch (ttype(o)) { case LUA_TUSERDATA: return getudatamem(uvalue(o)); case LUA_TLIGHTUSERDATA: return pvalue(o); default: return NULL; @@ -420,23 +453,37 @@ LUA_API void *lua_touserdata (lua_State *L, int idx) { } +LUA_API void *lua_touserdata (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return touserdata(o); +} + + LUA_API lua_State *lua_tothread (lua_State *L, int idx) { - StkId o = index2addr(L, idx); + const TValue *o = index2value(L, idx); return (!ttisthread(o)) ? NULL : thvalue(o); } +/* +** Returns a pointer to the internal representation of an object. +** Note that ANSI C does not allow the conversion of a pointer to +** function to a 'void*', so the conversion here goes through +** a 'size_t'. (As the returned pointer is only informative, this +** conversion should not be a problem.) +*/ LUA_API const void *lua_topointer (lua_State *L, int idx) { - StkId o = index2addr(L, idx); - switch (ttype(o)) { - case LUA_TTABLE: return hvalue(o); - case LUA_TLCL: return clLvalue(o); - case LUA_TCCL: return clCvalue(o); - case LUA_TLCF: return cast(void *, cast(size_t, fvalue(o))); - case LUA_TTHREAD: return thvalue(o); - case LUA_TUSERDATA: return getudatamem(uvalue(o)); - case LUA_TLIGHTUSERDATA: return pvalue(o); - default: return NULL; + const TValue *o = index2value(L, idx); + switch (ttypetag(o)) { + case LUA_VLCF: return cast_voidp(cast_sizet(fvalue(o))); + case LUA_VUSERDATA: case LUA_VLIGHTUSERDATA: + return touserdata(o); + default: { + if (iscollectable(o)) + return gcvalue(o); + else + return NULL; + } } } @@ -449,7 +496,7 @@ LUA_API const void *lua_topointer (lua_State *L, int idx) { LUA_API void lua_pushnil (lua_State *L) { lua_lock(L); - setnilvalue(L->top); + setnilvalue(s2v(L->top.p)); api_incr_top(L); lua_unlock(L); } @@ -457,7 +504,7 @@ LUA_API void lua_pushnil (lua_State *L) { LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { lua_lock(L); - setfltvalue(L->top, n); + setfltvalue(s2v(L->top.p), n); api_incr_top(L); lua_unlock(L); } @@ -465,19 +512,24 @@ LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { lua_lock(L); - setivalue(L->top, n); + setivalue(s2v(L->top.p), n); api_incr_top(L); lua_unlock(L); } +/* +** Pushes on the stack a string with given length. Avoid using 's' when +** 'len' == 0 (as 's' can be NULL in that case), due to later use of +** 'memcmp' and 'memcpy'. +*/ LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { TString *ts; lua_lock(L); - luaC_checkGC(L); - ts = luaS_newlstr(L, s, len); - setsvalue2s(L, L->top, ts); + ts = (len == 0) ? luaS_new(L, "") : luaS_newlstr(L, s, len); + setsvalue2s(L, L->top.p, ts); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return getstr(ts); } @@ -486,15 +538,15 @@ LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { LUA_API const char *lua_pushstring (lua_State *L, const char *s) { lua_lock(L); if (s == NULL) - setnilvalue(L->top); + setnilvalue(s2v(L->top.p)); else { TString *ts; - luaC_checkGC(L); ts = luaS_new(L, s); - setsvalue2s(L, L->top, ts); + setsvalue2s(L, L->top.p, ts); s = getstr(ts); /* internal copy's address */ } api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return s; } @@ -504,8 +556,8 @@ LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp) { const char *ret; lua_lock(L); - luaC_checkGC(L); ret = luaO_pushvfstring(L, fmt, argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -515,10 +567,10 @@ LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { const char *ret; va_list argp; lua_lock(L); - luaC_checkGC(L); va_start(argp, fmt); ret = luaO_pushvfstring(L, fmt, argp); va_end(argp); + luaC_checkGC(L); lua_unlock(L); return ret; } @@ -527,30 +579,35 @@ LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { lua_lock(L); if (n == 0) { - setfvalue(L->top, fn); + setfvalue(s2v(L->top.p), fn); + api_incr_top(L); } else { CClosure *cl; api_checknelems(L, n); api_check(L, n <= MAXUPVAL, "upvalue index too large"); - luaC_checkGC(L); cl = luaF_newCclosure(L, n); cl->f = fn; - L->top -= n; + L->top.p -= n; while (n--) { - setobj2n(L, &cl->upvalue[n], L->top + n); + setobj2n(L, &cl->upvalue[n], s2v(L->top.p + n)); /* does not need barrier because closure is white */ + lua_assert(iswhite(cl)); } - setclCvalue(L, L->top, cl); + setclCvalue(L, s2v(L->top.p), cl); + api_incr_top(L); + luaC_checkGC(L); } - api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushboolean (lua_State *L, int b) { lua_lock(L); - setbvalue(L->top, (b != 0)); /* ensure that true is 1 */ + if (b) + setbtvalue(s2v(L->top.p)); + else + setbfvalue(s2v(L->top.p)); api_incr_top(L); lua_unlock(L); } @@ -558,7 +615,7 @@ LUA_API void lua_pushboolean (lua_State *L, int b) { LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { lua_lock(L); - setpvalue(L->top, p); + setpvalue(s2v(L->top.p), p); api_incr_top(L); lua_unlock(L); } @@ -566,7 +623,7 @@ LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { LUA_API int lua_pushthread (lua_State *L) { lua_lock(L); - setthvalue(L, L->top, L); + setthvalue(L, s2v(L->top.p), L); api_incr_top(L); lua_unlock(L); return (G(L)->mainthread == L); @@ -579,99 +636,138 @@ LUA_API int lua_pushthread (lua_State *L) { */ +l_sinline int auxgetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + setobj2s(L, L->top.p, slot); + api_incr_top(L); + } + else { + setsvalue2s(L, L->top.p, str); + api_incr_top(L); + luaV_finishget(L, t, s2v(L->top.p - 1), L->top.p - 1, slot); + } + lua_unlock(L); + return ttype(s2v(L->top.p - 1)); +} + + +/* +** Get the global table in the registry. Since all predefined +** indices in the registry were inserted right when the registry +** was created and never removed, they must always be in the array +** part of the registry. +*/ +#define getGtable(L) \ + (&hvalue(&G(L)->l_registry)->array[LUA_RIDX_GLOBALS - 1]) + + LUA_API int lua_getglobal (lua_State *L, const char *name) { - Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ + const TValue *G; lua_lock(L); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top, luaS_new(L, name)); - api_incr_top(L); - luaV_gettable(L, gt, L->top - 1, L->top - 1); - lua_unlock(L); - return ttnov(L->top - 1); + G = getGtable(L); + return auxgetstr(L, G, name); } LUA_API int lua_gettable (lua_State *L, int idx) { - StkId t; + const TValue *slot; + TValue *t; lua_lock(L); - t = index2addr(L, idx); - luaV_gettable(L, t, L->top - 1, L->top - 1); + t = index2value(L, idx); + if (luaV_fastget(L, t, s2v(L->top.p - 1), slot, luaH_get)) { + setobj2s(L, L->top.p - 1, slot); + } + else + luaV_finishget(L, t, s2v(L->top.p - 1), L->top.p - 1, slot); lua_unlock(L); - return ttnov(L->top - 1); + return ttype(s2v(L->top.p - 1)); } LUA_API int lua_getfield (lua_State *L, int idx, const char *k) { - StkId t; lua_lock(L); - t = index2addr(L, idx); - setsvalue2s(L, L->top, luaS_new(L, k)); - api_incr_top(L); - luaV_gettable(L, t, L->top - 1, L->top - 1); - lua_unlock(L); - return ttnov(L->top - 1); + return auxgetstr(L, index2value(L, idx), k); } LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) { - StkId t; + TValue *t; + const TValue *slot; lua_lock(L); - t = index2addr(L, idx); - setivalue(L->top, n); + t = index2value(L, idx); + if (luaV_fastgeti(L, t, n, slot)) { + setobj2s(L, L->top.p, slot); + } + else { + TValue aux; + setivalue(&aux, n); + luaV_finishget(L, t, &aux, L->top.p, slot); + } + api_incr_top(L); + lua_unlock(L); + return ttype(s2v(L->top.p - 1)); +} + + +l_sinline int finishrawget (lua_State *L, const TValue *val) { + if (isempty(val)) /* avoid copying empty items to the stack */ + setnilvalue(s2v(L->top.p)); + else + setobj2s(L, L->top.p, val); api_incr_top(L); - luaV_gettable(L, t, L->top - 1, L->top - 1); lua_unlock(L); - return ttnov(L->top - 1); + return ttype(s2v(L->top.p - 1)); +} + + +static Table *gettable (lua_State *L, int idx) { + TValue *t = index2value(L, idx); + api_check(L, ttistable(t), "table expected"); + return hvalue(t); } LUA_API int lua_rawget (lua_State *L, int idx) { - StkId t; + Table *t; + const TValue *val; lua_lock(L); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); - lua_unlock(L); - return ttnov(L->top - 1); + api_checknelems(L, 1); + t = gettable(L, idx); + val = luaH_get(t, s2v(L->top.p - 1)); + L->top.p--; /* remove key */ + return finishrawget(L, val); } LUA_API int lua_rawgeti (lua_State *L, int idx, lua_Integer n) { - StkId t; + Table *t; lua_lock(L); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - setobj2s(L, L->top, luaH_getint(hvalue(t), n)); - api_incr_top(L); - lua_unlock(L); - return ttnov(L->top - 1); + t = gettable(L, idx); + return finishrawget(L, luaH_getint(t, n)); } LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) { - StkId t; + Table *t; TValue k; lua_lock(L); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - setpvalue(&k, cast(void *, p)); - setobj2s(L, L->top, luaH_get(hvalue(t), &k)); - api_incr_top(L); - lua_unlock(L); - return ttnov(L->top - 1); + t = gettable(L, idx); + setpvalue(&k, cast_voidp(p)); + return finishrawget(L, luaH_get(t, &k)); } LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { Table *t; lua_lock(L); - luaC_checkGC(L); t = luaH_new(L); - sethvalue(L, L->top, t); + sethvalue2s(L, L->top.p, t); api_incr_top(L); if (narray > 0 || nrec > 0) luaH_resize(L, t, narray, nrec); + luaC_checkGC(L); lua_unlock(L); } @@ -681,8 +777,8 @@ LUA_API int lua_getmetatable (lua_State *L, int objindex) { Table *mt; int res = 0; lua_lock(L); - obj = index2addr(L, objindex); - switch (ttnov(obj)) { + obj = index2value(L, objindex); + switch (ttype(obj)) { case LUA_TTABLE: mt = hvalue(obj)->metatable; break; @@ -690,11 +786,11 @@ LUA_API int lua_getmetatable (lua_State *L, int objindex) { mt = uvalue(obj)->metatable; break; default: - mt = G(L)->mt[ttnov(obj)]; + mt = G(L)->mt[ttype(obj)]; break; } if (mt != NULL) { - sethvalue(L, L->top, mt); + sethvalue2s(L, L->top.p, mt); api_incr_top(L); res = 1; } @@ -703,15 +799,23 @@ LUA_API int lua_getmetatable (lua_State *L, int objindex) { } -LUA_API int lua_getuservalue (lua_State *L, int idx) { - StkId o; +LUA_API int lua_getiuservalue (lua_State *L, int idx, int n) { + TValue *o; + int t; lua_lock(L); - o = index2addr(L, idx); + o = index2value(L, idx); api_check(L, ttisfulluserdata(o), "full userdata expected"); - getuservalue(L, uvalue(o), L->top); + if (n <= 0 || n > uvalue(o)->nuvalue) { + setnilvalue(s2v(L->top.p)); + t = LUA_TNONE; + } + else { + setobj2s(L, L->top.p, &uvalue(o)->uv[n - 1].uv); + t = ttype(s2v(L->top.p)); + } api_incr_top(L); lua_unlock(L); - return ttnov(L->top - 1); + return t; } @@ -719,102 +823,109 @@ LUA_API int lua_getuservalue (lua_State *L, int idx) { ** set functions (stack -> Lua) */ +/* +** t[k] = value at the top of the stack (where 'k' is a string) +*/ +static void auxsetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + api_checknelems(L, 1); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); + L->top.p--; /* pop value */ + } + else { + setsvalue2s(L, L->top.p, str); /* push 'str' (to make it a TValue) */ + api_incr_top(L); + luaV_finishset(L, t, s2v(L->top.p - 1), s2v(L->top.p - 2), slot); + L->top.p -= 2; /* pop value and key */ + } + lua_unlock(L); /* lock done by caller */ +} + LUA_API void lua_setglobal (lua_State *L, const char *name) { - Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt; /* global table */ - lua_lock(L); - api_checknelems(L, 1); - gt = luaH_getint(reg, LUA_RIDX_GLOBALS); - setsvalue2s(L, L->top, luaS_new(L, name)); - api_incr_top(L); - luaV_settable(L, gt, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ - lua_unlock(L); + const TValue *G; + lua_lock(L); /* unlock done in 'auxsetstr' */ + G = getGtable(L); + auxsetstr(L, G, name); } LUA_API void lua_settable (lua_State *L, int idx) { - StkId t; + TValue *t; + const TValue *slot; lua_lock(L); api_checknelems(L, 2); - t = index2addr(L, idx); - luaV_settable(L, t, L->top - 2, L->top - 1); - L->top -= 2; /* pop index and value */ + t = index2value(L, idx); + if (luaV_fastget(L, t, s2v(L->top.p - 2), slot, luaH_get)) { + luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); + } + else + luaV_finishset(L, t, s2v(L->top.p - 2), s2v(L->top.p - 1), slot); + L->top.p -= 2; /* pop index and value */ lua_unlock(L); } LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { - StkId t; - lua_lock(L); - api_checknelems(L, 1); - t = index2addr(L, idx); - setsvalue2s(L, L->top, luaS_new(L, k)); - api_incr_top(L); - luaV_settable(L, t, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ - lua_unlock(L); + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, index2value(L, idx), k); } LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { - StkId t; + TValue *t; + const TValue *slot; lua_lock(L); api_checknelems(L, 1); - t = index2addr(L, idx); - setivalue(L->top, n); - api_incr_top(L); - luaV_settable(L, t, L->top - 1, L->top - 2); - L->top -= 2; /* pop value and key */ + t = index2value(L, idx); + if (luaV_fastgeti(L, t, n, slot)) { + luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); + } + else { + TValue aux; + setivalue(&aux, n); + luaV_finishset(L, t, &aux, s2v(L->top.p - 1), slot); + } + L->top.p--; /* pop value */ lua_unlock(L); } -LUA_API void lua_rawset (lua_State *L, int idx) { - StkId o; +static void aux_rawset (lua_State *L, int idx, TValue *key, int n) { Table *t; lua_lock(L); - api_checknelems(L, 2); - o = index2addr(L, idx); - api_check(L, ttistable(o), "table expected"); - t = hvalue(o); - setobj2t(L, luaH_set(L, t, L->top-2), L->top-1); + api_checknelems(L, n); + t = gettable(L, idx); + luaH_set(L, t, key, s2v(L->top.p - 1)); invalidateTMcache(t); - luaC_barrierback(L, t, L->top-1); - L->top -= 2; + luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1)); + L->top.p -= n; lua_unlock(L); } -LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { - StkId o; - Table *t; - lua_lock(L); - api_checknelems(L, 1); - o = index2addr(L, idx); - api_check(L, ttistable(o), "table expected"); - t = hvalue(o); - luaH_setint(L, t, n, L->top - 1); - luaC_barrierback(L, t, L->top-1); - L->top--; - lua_unlock(L); +LUA_API void lua_rawset (lua_State *L, int idx) { + aux_rawset(L, idx, s2v(L->top.p - 2), 2); } LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { - StkId o; - Table *t; TValue k; + setpvalue(&k, cast_voidp(p)); + aux_rawset(L, idx, &k, 1); +} + + +LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { + Table *t; lua_lock(L); api_checknelems(L, 1); - o = index2addr(L, idx); - api_check(L, ttistable(o), "table expected"); - t = hvalue(o); - setpvalue(&k, cast(void *, p)); - setobj2t(L, luaH_set(L, t, &k), L->top - 1); - luaC_barrierback(L, t, L->top - 1); - L->top--; + t = gettable(L, idx); + luaH_setint(L, t, n, s2v(L->top.p - 1)); + luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1)); + L->top.p--; lua_unlock(L); } @@ -824,14 +935,14 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) { Table *mt; lua_lock(L); api_checknelems(L, 1); - obj = index2addr(L, objindex); - if (ttisnil(L->top - 1)) + obj = index2value(L, objindex); + if (ttisnil(s2v(L->top.p - 1))) mt = NULL; else { - api_check(L, ttistable(L->top - 1), "table expected"); - mt = hvalue(L->top - 1); + api_check(L, ttistable(s2v(L->top.p - 1)), "table expected"); + mt = hvalue(s2v(L->top.p - 1)); } - switch (ttnov(obj)) { + switch (ttype(obj)) { case LUA_TTABLE: { hvalue(obj)->metatable = mt; if (mt) { @@ -849,26 +960,33 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) { break; } default: { - G(L)->mt[ttnov(obj)] = mt; + G(L)->mt[ttype(obj)] = mt; break; } } - L->top--; + L->top.p--; lua_unlock(L); return 1; } -LUA_API void lua_setuservalue (lua_State *L, int idx) { - StkId o; +LUA_API int lua_setiuservalue (lua_State *L, int idx, int n) { + TValue *o; + int res; lua_lock(L); api_checknelems(L, 1); - o = index2addr(L, idx); + o = index2value(L, idx); api_check(L, ttisfulluserdata(o), "full userdata expected"); - setuservalue(L, uvalue(o), L->top - 1); - luaC_barrier(L, gcvalue(o), L->top - 1); - L->top--; + if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue))) + res = 0; /* 'n' not in [1, uvalue(o)->nuvalue] */ + else { + setobj(L, &uvalue(o)->uv[n - 1].uv, s2v(L->top.p - 1)); + luaC_barrierback(L, gcvalue(o), s2v(L->top.p - 1)); + res = 1; + } + L->top.p--; lua_unlock(L); + return res; } @@ -878,7 +996,8 @@ LUA_API void lua_setuservalue (lua_State *L, int idx) { #define checkresults(L,na,nr) \ - api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na)), \ + api_check(L, (nr) == LUA_MULTRET \ + || (L->ci->top.p - L->top.p >= (nr) - (na)), \ "results from function overflow current stack size") @@ -891,14 +1010,14 @@ LUA_API void lua_callk (lua_State *L, int nargs, int nresults, api_checknelems(L, nargs+1); api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); checkresults(L, nargs, nresults); - func = L->top - (nargs+1); - if (k != NULL && L->nny == 0) { /* need to prepare continuation? */ + func = L->top.p - (nargs+1); + if (k != NULL && yieldable(L)) { /* need to prepare continuation? */ L->ci->u.c.k = k; /* save continuation */ L->ci->u.c.ctx = ctx; /* save context */ - luaD_call(L, func, nresults, 1); /* do the call */ + luaD_call(L, func, nresults); /* do the call */ } else /* no continuation or no yieldable */ - luaD_call(L, func, nresults, 0); /* just do the call */ + luaD_callnoyield(L, func, nresults); /* just do the call */ adjustresults(L, nresults); lua_unlock(L); } @@ -916,7 +1035,7 @@ struct CallS { /* data to 'f_call' */ static void f_call (lua_State *L, void *ud) { struct CallS *c = cast(struct CallS *, ud); - luaD_call(L, c->func, c->nresults, 0); + luaD_callnoyield(L, c->func, c->nresults); } @@ -935,12 +1054,12 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, if (errfunc == 0) func = 0; else { - StkId o = index2addr(L, errfunc); - api_checkstackindex(L, errfunc, o); + StkId o = index2stack(L, errfunc); + api_check(L, ttisfunction(s2v(o)), "error handler must be a function"); func = savestack(L, o); } - c.func = L->top - (nargs+1); /* function to be called */ - if (k == NULL || L->nny > 0) { /* no continuation or no yieldable? */ + c.func = L->top.p - (nargs+1); /* function to be called */ + if (k == NULL || !yieldable(L)) { /* no continuation or no yieldable? */ c.nresults = nresults; /* do a 'conventional' protected call */ status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); } @@ -949,12 +1068,12 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, ci->u.c.k = k; /* save continuation */ ci->u.c.ctx = ctx; /* save context */ /* save information for error recovery */ - ci->extra = savestack(L, c.func); + ci->u2.funcidx = cast_int(savestack(L, c.func)); ci->u.c.old_errfunc = L->errfunc; L->errfunc = func; setoah(ci->callstatus, L->allowhook); /* save value of 'allowhook' */ ci->callstatus |= CIST_YPCALL; /* function can do error recovery */ - luaD_call(L, c.func, nresults, 1); /* do the call */ + luaD_call(L, c.func, nresults); /* do the call */ ci->callstatus &= ~CIST_YPCALL; L->errfunc = ci->u.c.old_errfunc; status = LUA_OK; /* if it is here, there were no errors */ @@ -974,14 +1093,13 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, luaZ_init(L, &z, reader, data); status = luaD_protectedparser(L, &z, chunkname, mode); if (status == LUA_OK) { /* no errors? */ - LClosure *f = clLvalue(L->top - 1); /* get newly created function */ + LClosure *f = clLvalue(s2v(L->top.p - 1)); /* get new function */ if (f->nupvalues >= 1) { /* does it have an upvalue? */ /* get global table from registry */ - Table *reg = hvalue(&G(L)->l_registry); - const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS); + const TValue *gt = getGtable(L); /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */ - setobj(L, f->upvals[0]->v, gt); - luaC_upvalbarrier(L, f->upvals[0]); + setobj(L, f->upvals[0]->v.p, gt); + luaC_barrier(L, f->upvals[0], gt); } } lua_unlock(L); @@ -994,7 +1112,7 @@ LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { TValue *o; lua_lock(L); api_checknelems(L, 1); - o = L->top - 1; + o = s2v(L->top.p - 1); if (isLfunction(o)) status = luaU_dump(L, getproto(o), writer, data, strip); else @@ -1012,20 +1130,22 @@ LUA_API int lua_status (lua_State *L) { /* ** Garbage-collection function */ - -LUA_API int lua_gc (lua_State *L, int what, int data) { +LUA_API int lua_gc (lua_State *L, int what, ...) { + va_list argp; int res = 0; - global_State *g; + global_State *g = G(L); + if (g->gcstp & GCSTPGC) /* internal stop? */ + return -1; /* all options are invalid when stopped */ lua_lock(L); - g = G(L); + va_start(argp, what); switch (what) { case LUA_GCSTOP: { - g->gcrunning = 0; + g->gcstp = GCSTPUSR; /* stopped by the user */ break; } case LUA_GCRESTART: { luaE_setdebt(g, 0); - g->gcrunning = 1; + g->gcstp = 0; /* (GCSTPGC must be already zero here) */ break; } case LUA_GCCOLLECT: { @@ -1042,11 +1162,12 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { break; } case LUA_GCSTEP: { + int data = va_arg(argp, int); l_mem debt = 1; /* =1 to signal that it did an actual step */ - int oldrunning = g->gcrunning; - g->gcrunning = 1; /* allow GC to run */ + lu_byte oldstp = g->gcstp; + g->gcstp = 0; /* allow GC to run (GCSTPGC must be zero here) */ if (data == 0) { - luaE_setdebt(g, -GCSTEPSIZE); /* to do a "small" step */ + luaE_setdebt(g, 0); /* do a basic step */ luaC_step(L); } else { /* add 'data' to total debt */ @@ -1054,28 +1175,55 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { luaE_setdebt(g, debt); luaC_checkGC(L); } - g->gcrunning = oldrunning; /* restore previous state */ + g->gcstp = oldstp; /* restore previous state */ if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ res = 1; /* signal it */ break; } case LUA_GCSETPAUSE: { - res = g->gcpause; - g->gcpause = data; + int data = va_arg(argp, int); + res = getgcparam(g->gcpause); + setgcparam(g->gcpause, data); break; } case LUA_GCSETSTEPMUL: { - res = g->gcstepmul; - if (data < 40) data = 40; /* avoid ridiculous low values (and 0) */ - g->gcstepmul = data; + int data = va_arg(argp, int); + res = getgcparam(g->gcstepmul); + setgcparam(g->gcstepmul, data); break; } case LUA_GCISRUNNING: { - res = g->gcrunning; + res = gcrunning(g); + break; + } + case LUA_GCGEN: { + int minormul = va_arg(argp, int); + int majormul = va_arg(argp, int); + res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; + if (minormul != 0) + g->genminormul = minormul; + if (majormul != 0) + setgcparam(g->genmajormul, majormul); + luaC_changemode(L, KGC_GEN); + break; + } + case LUA_GCINC: { + int pause = va_arg(argp, int); + int stepmul = va_arg(argp, int); + int stepsize = va_arg(argp, int); + res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; + if (pause != 0) + setgcparam(g->gcpause, pause); + if (stepmul != 0) + setgcparam(g->gcstepmul, stepmul); + if (stepsize != 0) + g->gcstepsize = stepsize; + luaC_changemode(L, KGC_INC); break; } default: res = -1; /* invalid option */ } + va_end(argp); lua_unlock(L); return res; } @@ -1088,52 +1236,71 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { LUA_API int lua_error (lua_State *L) { + TValue *errobj; lua_lock(L); + errobj = s2v(L->top.p - 1); api_checknelems(L, 1); - luaG_errormsg(L); + /* error object is the memory error message? */ + if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg)) + luaM_error(L); /* raise a memory error */ + else + luaG_errormsg(L); /* raise a regular error */ /* code unreachable; will unlock when control actually leaves the kernel */ return 0; /* to avoid warnings */ } LUA_API int lua_next (lua_State *L, int idx) { - StkId t; + Table *t; int more; lua_lock(L); - t = index2addr(L, idx); - api_check(L, ttistable(t), "table expected"); - more = luaH_next(L, hvalue(t), L->top - 1); + api_checknelems(L, 1); + t = gettable(L, idx); + more = luaH_next(L, t, L->top.p - 1); if (more) { api_incr_top(L); } else /* no more elements */ - L->top -= 1; /* remove key */ + L->top.p -= 1; /* remove key */ lua_unlock(L); return more; } +LUA_API void lua_toclose (lua_State *L, int idx) { + int nresults; + StkId o; + lua_lock(L); + o = index2stack(L, idx); + nresults = L->ci->nresults; + api_check(L, L->tbclist.p < o, "given index below or equal a marked one"); + luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ + if (!hastocloseCfunc(nresults)) /* function not marked yet? */ + L->ci->nresults = codeNresults(nresults); /* mark it */ + lua_assert(hastocloseCfunc(L->ci->nresults)); + lua_unlock(L); +} + + LUA_API void lua_concat (lua_State *L, int n) { lua_lock(L); api_checknelems(L, n); - if (n >= 2) { - luaC_checkGC(L); + if (n > 0) luaV_concat(L, n); - } - else if (n == 0) { /* push empty string */ - setsvalue2s(L, L->top, luaS_newlstr(L, "", 0)); + else { /* nothing to concatenate */ + setsvalue2s(L, L->top.p, luaS_newlstr(L, "", 0)); /* push empty string */ api_incr_top(L); } - /* else n == 1; nothing to do */ + luaC_checkGC(L); lua_unlock(L); } LUA_API void lua_len (lua_State *L, int idx) { - StkId t; + TValue *t; lua_lock(L); - t = index2addr(L, idx); - luaV_objlen(L, L->top, t); + t = index2value(L, idx); + luaV_objlen(L, L->top.p, t); api_incr_top(L); lua_unlock(L); } @@ -1157,38 +1324,57 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { } -LUA_API void *lua_newuserdata (lua_State *L, size_t size) { +void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) { + lua_lock(L); + G(L)->ud_warn = ud; + G(L)->warnf = f; + lua_unlock(L); +} + + +void lua_warning (lua_State *L, const char *msg, int tocont) { + lua_lock(L); + luaE_warning(L, msg, tocont); + lua_unlock(L); +} + + + +LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) { Udata *u; lua_lock(L); - luaC_checkGC(L); - u = luaS_newudata(L, size); - setuvalue(L, L->top, u); + api_check(L, 0 <= nuvalue && nuvalue < USHRT_MAX, "invalid value"); + u = luaS_newudata(L, size, nuvalue); + setuvalue(L, s2v(L->top.p), u); api_incr_top(L); + luaC_checkGC(L); lua_unlock(L); return getudatamem(u); } -static const char *aux_upvalue (StkId fi, int n, TValue **val, - CClosure **owner, UpVal **uv) { - switch (ttype(fi)) { - case LUA_TCCL: { /* C closure */ +static const char *aux_upvalue (TValue *fi, int n, TValue **val, + GCObject **owner) { + switch (ttypetag(fi)) { + case LUA_VCCL: { /* C closure */ CClosure *f = clCvalue(fi); - if (!(1 <= n && n <= f->nupvalues)) return NULL; + if (!(cast_uint(n) - 1u < cast_uint(f->nupvalues))) + return NULL; /* 'n' not in [1, f->nupvalues] */ *val = &f->upvalue[n-1]; - if (owner) *owner = f; + if (owner) *owner = obj2gco(f); return ""; } - case LUA_TLCL: { /* Lua closure */ + case LUA_VLCL: { /* Lua closure */ LClosure *f = clLvalue(fi); TString *name; Proto *p = f->p; - if (!(1 <= n && n <= p->sizeupvalues)) return NULL; - *val = f->upvals[n-1]->v; - if (uv) *uv = f->upvals[n - 1]; + if (!(cast_uint(n) - 1u < cast_uint(p->sizeupvalues))) + return NULL; /* 'n' not in [1, p->sizeupvalues] */ + *val = f->upvals[n-1]->v.p; + if (owner) *owner = obj2gco(f->upvals[n - 1]); name = p->upvalues[n-1].name; - return (name == NULL) ? "(*no name)" : getstr(name); + return (name == NULL) ? "(no name)" : getstr(name); } default: return NULL; /* not a closure */ } @@ -1199,9 +1385,9 @@ LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val = NULL; /* to avoid warnings */ lua_lock(L); - name = aux_upvalue(index2addr(L, funcindex), n, &val, NULL, NULL); + name = aux_upvalue(index2value(L, funcindex), n, &val, NULL); if (name) { - setobj2s(L, L->top, val); + setobj2s(L, L->top.p, val); api_incr_top(L); } lua_unlock(L); @@ -1212,18 +1398,16 @@ LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val = NULL; /* to avoid warnings */ - CClosure *owner = NULL; - UpVal *uv = NULL; - StkId fi; + GCObject *owner = NULL; /* to avoid warnings */ + TValue *fi; lua_lock(L); - fi = index2addr(L, funcindex); + fi = index2value(L, funcindex); api_checknelems(L, 1); - name = aux_upvalue(fi, n, &val, &owner, &uv); + name = aux_upvalue(fi, n, &val, &owner); if (name) { - L->top--; - setobj(L, val, L->top); - if (owner) { luaC_barrier(L, owner, L->top); } - else if (uv) { luaC_upvalbarrier(L, uv); } + L->top.p--; + setobj(L, val, s2v(L->top.p)); + luaC_barrier(L, owner, val); } lua_unlock(L); return name; @@ -1231,29 +1415,35 @@ LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { static UpVal **getupvalref (lua_State *L, int fidx, int n, LClosure **pf) { + static const UpVal *const nullup = NULL; LClosure *f; - StkId fi = index2addr(L, fidx); + TValue *fi = index2value(L, fidx); api_check(L, ttisLclosure(fi), "Lua function expected"); f = clLvalue(fi); - api_check(L, (1 <= n && n <= f->p->sizeupvalues), "invalid upvalue index"); if (pf) *pf = f; - return &f->upvals[n - 1]; /* get its upvalue pointer */ + if (1 <= n && n <= f->p->sizeupvalues) + return &f->upvals[n - 1]; /* get its upvalue pointer */ + else + return (UpVal**)&nullup; } LUA_API void *lua_upvalueid (lua_State *L, int fidx, int n) { - StkId fi = index2addr(L, fidx); - switch (ttype(fi)) { - case LUA_TLCL: { /* lua closure */ + TValue *fi = index2value(L, fidx); + switch (ttypetag(fi)) { + case LUA_VLCL: { /* lua closure */ return *getupvalref(L, fidx, n, NULL); } - case LUA_TCCL: { /* C closure */ + case LUA_VCCL: { /* C closure */ CClosure *f = clCvalue(fi); - api_check(L, 1 <= n && n <= f->nupvalues, "invalid upvalue index"); - return &f->upvalue[n - 1]; - } + if (1 <= n && n <= f->nupvalues) + return &f->upvalue[n - 1]; + /* else */ + } /* FALLTHROUGH */ + case LUA_VLCF: + return NULL; /* light C functions have no upvalues */ default: { - api_check(L, 0, "closure expected"); + api_check(L, 0, "function expected"); return NULL; } } @@ -1265,11 +1455,9 @@ LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1, LClosure *f1; UpVal **up1 = getupvalref(L, fidx1, n1, &f1); UpVal **up2 = getupvalref(L, fidx2, n2, NULL); - luaC_upvdeccount(L, *up1); + api_check(L, *up1 != NULL && *up2 != NULL, "invalid upvalue index"); *up1 = *up2; - (*up1)->refcount++; - if (upisopen(*up1)) (*up1)->u.open.touched = 1; - luaC_upvalbarrier(L, *up1); + luaC_objbarrier(L, f1, *up1); } diff --git a/libs/lua/lapi.h b/libs/lua/lapi.h index 6d36dee3..a742427c 100644 --- a/libs/lua/lapi.h +++ b/libs/lua/lapi.h @@ -1,5 +1,5 @@ /* -** $Id: lapi.h,v 2.9 2015/03/06 19:49:50 roberto Exp $ +** $Id: lapi.h $ ** Auxiliary functions from Lua API ** See Copyright Notice in lua.h */ @@ -11,14 +11,42 @@ #include "llimits.h" #include "lstate.h" -#define api_incr_top(L) {L->top++; api_check(L, L->top <= L->ci->top, \ - "stack overflow");} +/* Increments 'L->top.p', checking for stack overflows */ +#define api_incr_top(L) {L->top.p++; \ + api_check(L, L->top.p <= L->ci->top.p, \ + "stack overflow");} + + +/* +** If a call returns too many multiple returns, the callee may not have +** stack space to accommodate all results. In this case, this macro +** increases its stack space ('L->ci->top.p'). +*/ #define adjustresults(L,nres) \ - { if ((nres) == LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; } + { if ((nres) <= LUA_MULTRET && L->ci->top.p < L->top.p) \ + L->ci->top.p = L->top.p; } + + +/* Ensure the stack has at least 'n' elements */ +#define api_checknelems(L,n) \ + api_check(L, (n) < (L->top.p - L->ci->func.p), \ + "not enough elements in the stack") + + +/* +** To reduce the overhead of returning from C functions, the presence of +** to-be-closed variables in these functions is coded in the CallInfo's +** field 'nresults', in a way that functions with no to-be-closed variables +** with zero, one, or "all" wanted results have no overhead. Functions +** with other number of wanted results, as well as functions with +** variables to be closed, have an extra check. +*/ -#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \ - "not enough elements in the stack") +#define hastocloseCfunc(n) ((n) < LUA_MULTRET) +/* Map [-1, inf) (range of 'nresults') into (-inf, -2] */ +#define codeNresults(n) (-(n) - 3) +#define decodeNresults(n) (-(n) - 3) #endif diff --git a/libs/lua/lauxlib.c b/libs/lua/lauxlib.c index b8bace7f..923105ed 100644 --- a/libs/lua/lauxlib.c +++ b/libs/lua/lauxlib.c @@ -1,5 +1,5 @@ /* -** $Id: lauxlib.c,v 1.280 2015/02/03 17:38:24 roberto Exp $ +** $Id: lauxlib.c $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ @@ -17,7 +17,8 @@ #include -/* This file uses only the official API of Lua. +/* +** This file uses only the official API of Lua. ** Any function declared here could be written as an application function. */ @@ -26,6 +27,12 @@ #include "lauxlib.h" +#if !defined(MAX_SIZET) +/* maximum value for size_t */ +#define MAX_SIZET ((size_t)(~(size_t)0)) +#endif + + /* ** {====================================================== ** Traceback @@ -33,14 +40,14 @@ */ -#define LEVELS1 12 /* size of the first part of the stack */ -#define LEVELS2 10 /* size of the second part of the stack */ +#define LEVELS1 10 /* size of the first part of the stack */ +#define LEVELS2 11 /* size of the second part of the stack */ /* -** search for 'objidx' in table at index -1. -** return 1 + string at top if find a good name. +** Search for 'objidx' in table at index -1. ('objidx' must be an +** absolute index.) Return 1 + string at top if it found a good name. */ static int findfield (lua_State *L, int objidx, int level) { if (level == 0 || !lua_istable(L, -1)) @@ -53,10 +60,10 @@ static int findfield (lua_State *L, int objidx, int level) { return 1; } else if (findfield(L, objidx, level - 1)) { /* try recursively */ - lua_remove(L, -2); /* remove table (but keep name) */ - lua_pushliteral(L, "."); - lua_insert(L, -2); /* place '.' between the two names */ - lua_concat(L, 3); + /* stack: lib_name, lib_table, field_name (top) */ + lua_pushliteral(L, "."); /* place '.' between the two names */ + lua_replace(L, -3); /* (in the slot occupied by table) */ + lua_concat(L, 3); /* lib_name.field_name */ return 1; } } @@ -68,20 +75,20 @@ static int findfield (lua_State *L, int objidx, int level) { /* ** Search for a name for a function in all loaded modules -** (registry._LOADED). */ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { int top = lua_gettop(L); lua_getinfo(L, "f", ar); /* push function */ - lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + luaL_checkstack(L, 6, "not enough stack"); /* slots for 'findfield' */ if (findfield(L, top + 1, 2)) { const char *name = lua_tostring(L, -1); - if (strncmp(name, "_G.", 3) == 0) { /* name start with '_G.'? */ + if (strncmp(name, LUA_GNAME ".", 3) == 0) { /* name start with '_G.'? */ lua_pushstring(L, name + 3); /* push name without prefix */ lua_remove(L, -2); /* remove original name */ } - lua_copy(L, -1, top + 1); /* move name to proper place */ - lua_pop(L, 2); /* remove pushed values */ + lua_copy(L, -1, top + 1); /* copy name to proper place */ + lua_settop(L, top + 1); /* remove table "loaded" and name copy */ return 1; } else { @@ -107,7 +114,7 @@ static void pushfuncname (lua_State *L, lua_Debug *ar) { } -static int countlevels (lua_State *L) { +static int lastlevel (lua_State *L) { lua_Debug ar; int li = 1, le = 1; /* find an upper bound */ @@ -124,30 +131,37 @@ static int countlevels (lua_State *L) { LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level) { + luaL_Buffer b; lua_Debug ar; - int top = lua_gettop(L); - int numlevels = countlevels(L1); - int mark = (numlevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; - if (msg) lua_pushfstring(L, "%s\n", msg); - lua_pushliteral(L, "stack traceback:"); + int last = lastlevel(L1); + int limit2show = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1; + luaL_buffinit(L, &b); + if (msg) { + luaL_addstring(&b, msg); + luaL_addchar(&b, '\n'); + } + luaL_addstring(&b, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { - if (level == mark) { /* too many levels? */ - lua_pushliteral(L, "\n\t..."); /* add a '...' */ - level = numlevels - LEVELS2; /* and skip to last ones */ + if (limit2show-- == 0) { /* too many levels? */ + int n = last - level - LEVELS2 + 1; /* number of levels to skip */ + lua_pushfstring(L, "\n\t...\t(skipping %d levels)", n); + luaL_addvalue(&b); /* add warning about skip */ + level += n; /* and skip to last levels */ } else { lua_getinfo(L1, "Slnt", &ar); - lua_pushfstring(L, "\n\t%s:", ar.short_src); - if (ar.currentline > 0) - lua_pushfstring(L, "%d:", ar.currentline); - lua_pushliteral(L, " in "); + if (ar.currentline <= 0) + lua_pushfstring(L, "\n\t%s: in ", ar.short_src); + else + lua_pushfstring(L, "\n\t%s:%d: in ", ar.short_src, ar.currentline); + luaL_addvalue(&b); pushfuncname(L, &ar); + luaL_addvalue(&b); if (ar.istailcall) - lua_pushliteral(L, "\n\t(...tail calls...)"); - lua_concat(L, lua_gettop(L) - top); + luaL_addstring(&b, "\n\t(...tail calls...)"); } } - lua_concat(L, lua_gettop(L) - top); + luaL_pushresult(&b); } /* }====================================================== */ @@ -177,7 +191,7 @@ LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { } -static int typeerror (lua_State *L, int arg, const char *tname) { +LUALIB_API int luaL_typeerror (lua_State *L, int arg, const char *tname) { const char *msg; const char *typearg; /* name for the type of the actual argument */ if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING) @@ -192,10 +206,14 @@ static int typeerror (lua_State *L, int arg, const char *tname) { static void tag_error (lua_State *L, int arg, int tag) { - typeerror(L, arg, lua_typename(L, tag)); + luaL_typeerror(L, arg, lua_typename(L, tag)); } +/* +** The use of 'lua_pushfstring' ensures this function does not +** need reserved stack space when called. +*/ LUALIB_API void luaL_where (lua_State *L, int level) { lua_Debug ar; if (lua_getstack(L, level, &ar)) { /* check function at level */ @@ -205,10 +223,15 @@ LUALIB_API void luaL_where (lua_State *L, int level) { return; } } - lua_pushliteral(L, ""); /* else, no information available... */ + lua_pushfstring(L, ""); /* else, no information available... */ } +/* +** Again, the use of 'lua_pushvfstring' ensures this function does +** not need reserved stack space when called. (At worst, it generates +** an error with "stack overflow" instead of the given message.) +*/ LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); @@ -227,11 +250,13 @@ LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { return 1; } else { - lua_pushnil(L); + const char *msg; + luaL_pushfail(L); + msg = (en != 0) ? strerror(en) : "(no extra info)"; if (fname) - lua_pushfstring(L, "%s: %s", fname, strerror(en)); + lua_pushfstring(L, "%s: %s", fname, msg); else - lua_pushstring(L, strerror(en)); + lua_pushstring(L, msg); lua_pushinteger(L, en); return 3; } @@ -261,24 +286,25 @@ LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { LUALIB_API int luaL_execresult (lua_State *L, int stat) { - const char *what = "exit"; /* type of termination */ - if (stat == -1) /* error? */ + if (stat != 0 && errno != 0) /* error with an 'errno'? */ return luaL_fileresult(L, 0, NULL); else { + const char *what = "exit"; /* type of termination */ l_inspectstat(stat, what); /* interpret result */ if (*what == 'e' && stat == 0) /* successful termination? */ lua_pushboolean(L, 1); else - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, what); lua_pushinteger(L, stat); - return 3; /* return true/nil,what,code */ + return 3; /* return true/fail,what,code */ } } /* }====================================================== */ + /* ** {====================================================== ** Userdata's metatable manipulation @@ -289,7 +315,7 @@ LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { if (luaL_getmetatable(L, tname) != LUA_TNIL) /* name already in use? */ return 0; /* leave previous value on top, but return 0 */ lua_pop(L, 1); - lua_newtable(L); /* create metatable */ + lua_createtable(L, 0, 2); /* create metatable */ lua_pushstring(L, tname); lua_setfield(L, -2, "__name"); /* metatable.__name = tname */ lua_pushvalue(L, -1); @@ -321,7 +347,7 @@ LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) { LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { void *p = luaL_testudata(L, ud, tname); - if (p == NULL) typeerror(L, ud, tname); + luaL_argexpected(L, p != NULL, ud, tname); return p; } @@ -347,10 +373,15 @@ LUALIB_API int luaL_checkoption (lua_State *L, int arg, const char *def, } +/* +** Ensures the stack has at least 'space' extra slots, raising an error +** if it cannot fulfill the request. (The error handling needs a few +** extra slots to format the error message. In case of an error without +** this extra space, Lua will generate the same 'stack overflow' error, +** but without 'msg'.) +*/ LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { - /* keep some extra space to run error routines, if needed */ - const int extra = LUA_MINSTACK; - if (!lua_checkstack(L, space + extra)) { + if (l_unlikely(!lua_checkstack(L, space))) { if (msg) luaL_error(L, "stack overflow (%s)", msg); else @@ -360,20 +391,20 @@ LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { LUALIB_API void luaL_checktype (lua_State *L, int arg, int t) { - if (lua_type(L, arg) != t) + if (l_unlikely(lua_type(L, arg) != t)) tag_error(L, arg, t); } LUALIB_API void luaL_checkany (lua_State *L, int arg) { - if (lua_type(L, arg) == LUA_TNONE) + if (l_unlikely(lua_type(L, arg) == LUA_TNONE)) luaL_argerror(L, arg, "value expected"); } LUALIB_API const char *luaL_checklstring (lua_State *L, int arg, size_t *len) { const char *s = lua_tolstring(L, arg, len); - if (!s) tag_error(L, arg, LUA_TSTRING); + if (l_unlikely(!s)) tag_error(L, arg, LUA_TSTRING); return s; } @@ -392,7 +423,7 @@ LUALIB_API const char *luaL_optlstring (lua_State *L, int arg, LUALIB_API lua_Number luaL_checknumber (lua_State *L, int arg) { int isnum; lua_Number d = lua_tonumberx(L, arg, &isnum); - if (!isnum) + if (l_unlikely(!isnum)) tag_error(L, arg, LUA_TNUMBER); return d; } @@ -414,7 +445,7 @@ static void interror (lua_State *L, int arg) { LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int arg) { int isnum; lua_Integer d = lua_tointegerx(L, arg, &isnum); - if (!isnum) { + if (l_unlikely(!isnum)) { interror(L, arg); } return d; @@ -435,42 +466,126 @@ LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int arg, ** ======================================================= */ +/* userdata to box arbitrary data */ +typedef struct UBox { + void *box; + size_t bsize; +} UBox; + + +static void *resizebox (lua_State *L, int idx, size_t newsize) { + void *ud; + lua_Alloc allocf = lua_getallocf(L, &ud); + UBox *box = (UBox *)lua_touserdata(L, idx); + void *temp = allocf(ud, box->box, box->bsize, newsize); + if (l_unlikely(temp == NULL && newsize > 0)) { /* allocation error? */ + lua_pushliteral(L, "not enough memory"); + lua_error(L); /* raise a memory error */ + } + box->box = temp; + box->bsize = newsize; + return temp; +} + + +static int boxgc (lua_State *L) { + resizebox(L, 1, 0); + return 0; +} + + +static const luaL_Reg boxmt[] = { /* box metamethods */ + {"__gc", boxgc}, + {"__close", boxgc}, + {NULL, NULL} +}; + + +static void newbox (lua_State *L) { + UBox *box = (UBox *)lua_newuserdatauv(L, sizeof(UBox), 0); + box->box = NULL; + box->bsize = 0; + if (luaL_newmetatable(L, "_UBOX*")) /* creating metatable? */ + luaL_setfuncs(L, boxmt, 0); /* set its metamethods */ + lua_setmetatable(L, -2); +} + + /* ** check whether buffer is using a userdata on the stack as a temporary ** buffer */ -#define buffonstack(B) ((B)->b != (B)->initb) +#define buffonstack(B) ((B)->b != (B)->init.b) /* -** returns a pointer to a free area with at least 'sz' bytes +** Whenever buffer is accessed, slot 'idx' must either be a box (which +** cannot be NULL) or it is a placeholder for the buffer. */ -LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { - lua_State *L = B->L; - if (B->size - B->n < sz) { /* not enough space? */ +#define checkbufferlevel(B,idx) \ + lua_assert(buffonstack(B) ? lua_touserdata(B->L, idx) != NULL \ + : lua_touserdata(B->L, idx) == (void*)B) + + +/* +** Compute new size for buffer 'B', enough to accommodate extra 'sz' +** bytes. (The test for "not big enough" also gets the case when the +** computation of 'newsize' overflows.) +*/ +static size_t newbuffsize (luaL_Buffer *B, size_t sz) { + size_t newsize = (B->size / 2) * 3; /* buffer size * 1.5 */ + if (l_unlikely(MAX_SIZET - sz < B->n)) /* overflow in (B->n + sz)? */ + return luaL_error(B->L, "buffer too large"); + if (newsize < B->n + sz) /* not big enough? */ + newsize = B->n + sz; + return newsize; +} + + +/* +** Returns a pointer to a free area with at least 'sz' bytes in buffer +** 'B'. 'boxidx' is the relative position in the stack where is the +** buffer's box or its placeholder. +*/ +static char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) { + checkbufferlevel(B, boxidx); + if (B->size - B->n >= sz) /* enough space? */ + return B->b + B->n; + else { + lua_State *L = B->L; char *newbuff; - size_t newsize = B->size * 2; /* double buffer size */ - if (newsize - B->n < sz) /* not big enough? */ - newsize = B->n + sz; - if (newsize < B->n || newsize - B->n < sz) - luaL_error(L, "buffer too large"); + size_t newsize = newbuffsize(B, sz); /* create larger buffer */ - newbuff = (char *)lua_newuserdata(L, newsize * sizeof(char)); - /* move content to new buffer */ - memcpy(newbuff, B->b, B->n * sizeof(char)); - if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + if (buffonstack(B)) /* buffer already has a box? */ + newbuff = (char *)resizebox(L, boxidx, newsize); /* resize it */ + else { /* no box yet */ + lua_remove(L, boxidx); /* remove placeholder */ + newbox(L); /* create a new box */ + lua_insert(L, boxidx); /* move box to its intended position */ + lua_toclose(L, boxidx); + newbuff = (char *)resizebox(L, boxidx, newsize); + memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */ + } B->b = newbuff; B->size = newsize; + return newbuff + B->n; } - return &B->b[B->n]; +} + +/* +** returns a pointer to a free area with at least 'sz' bytes +*/ +LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { + return prepbuffsize(B, sz, -1); } LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { - char *b = luaL_prepbuffsize(B, l); - memcpy(b, s, l * sizeof(char)); - luaL_addsize(B, l); + if (l > 0) { /* avoid 'memcpy' when 's' can be NULL */ + char *b = prepbuffsize(B, l, -1); + memcpy(b, s, l * sizeof(char)); + luaL_addsize(B, l); + } } @@ -481,9 +596,11 @@ LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { LUALIB_API void luaL_pushresult (luaL_Buffer *B) { lua_State *L = B->L; + checkbufferlevel(B, -1); lua_pushlstring(L, B->b, B->n); if (buffonstack(B)) - lua_remove(L, -2); /* remove old buffer */ + lua_closeslot(L, -2); /* close the box */ + lua_remove(L, -2); /* remove box or placeholder from the stack */ } @@ -493,28 +610,38 @@ LUALIB_API void luaL_pushresultsize (luaL_Buffer *B, size_t sz) { } +/* +** 'luaL_addvalue' is the only function in the Buffer system where the +** box (if existent) is not on the top of the stack. So, instead of +** calling 'luaL_addlstring', it replicates the code using -2 as the +** last argument to 'prepbuffsize', signaling that the box is (or will +** be) below the string being added to the buffer. (Box creation can +** trigger an emergency GC, so we should not remove the string from the +** stack before we have the space guaranteed.) +*/ LUALIB_API void luaL_addvalue (luaL_Buffer *B) { lua_State *L = B->L; - size_t l; - const char *s = lua_tolstring(L, -1, &l); - if (buffonstack(B)) - lua_insert(L, -2); /* put value below buffer */ - luaL_addlstring(B, s, l); - lua_remove(L, (buffonstack(B)) ? -2 : -1); /* remove value */ + size_t len; + const char *s = lua_tolstring(L, -1, &len); + char *b = prepbuffsize(B, len, -2); + memcpy(b, s, len * sizeof(char)); + luaL_addsize(B, len); + lua_pop(L, 1); /* pop string */ } LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) { B->L = L; - B->b = B->initb; + B->b = B->init.b; B->n = 0; B->size = LUAL_BUFFERSIZE; + lua_pushlightuserdata(L, (void*)B); /* push placeholder */ } LUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { luaL_buffinit(L, B); - return luaL_prepbuffsize(B, sz); + return prepbuffsize(B, sz, -1); } /* }====================================================== */ @@ -526,10 +653,14 @@ LUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { ** ======================================================= */ -/* index of free-list header */ -#define freelist 0 - +/* index of free-list header (after the predefined values) */ +#define freelist (LUA_RIDX_LAST + 1) +/* +** The previously freed references form a linked list: +** t[freelist] is the index of a first free index, or zero if list is +** empty; t[t[freelist]] is the index of the second element; etc. +*/ LUALIB_API int luaL_ref (lua_State *L, int t) { int ref; if (lua_isnil(L, -1)) { @@ -537,9 +668,16 @@ LUALIB_API int luaL_ref (lua_State *L, int t) { return LUA_REFNIL; /* 'nil' has a unique fixed reference */ } t = lua_absindex(L, t); - lua_rawgeti(L, t, freelist); /* get first free element */ - ref = (int)lua_tointeger(L, -1); /* ref = t[freelist] */ - lua_pop(L, 1); /* remove it from stack */ + if (lua_rawgeti(L, t, freelist) == LUA_TNIL) { /* first access? */ + ref = 0; /* list is empty */ + lua_pushinteger(L, 0); /* initialize as an empty list */ + lua_rawseti(L, t, freelist); /* ref = t[freelist] = 0 */ + } + else { /* already initialized */ + lua_assert(lua_isinteger(L, -1)); + ref = (int)lua_tointeger(L, -1); /* ref = t[freelist] */ + } + lua_pop(L, 1); /* remove element from stack */ if (ref != 0) { /* any free element? */ lua_rawgeti(L, t, ref); /* remove it from list */ lua_rawseti(L, t, freelist); /* (t[freelist] = t[ref]) */ @@ -555,6 +693,7 @@ LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { if (ref >= 0) { t = lua_absindex(L, t); lua_rawgeti(L, t, freelist); + lua_assert(lua_isinteger(L, -1)); lua_rawseti(L, t, ref); /* t[ref] = t[freelist] */ lua_pushinteger(L, ref); lua_rawseti(L, t, freelist); /* t[freelist] = ref */ @@ -596,25 +735,29 @@ static const char *getF (lua_State *L, void *ud, size_t *size) { static int errfile (lua_State *L, const char *what, int fnameindex) { - const char *serr = strerror(errno); + int err = errno; const char *filename = lua_tostring(L, fnameindex) + 1; - lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); + if (err != 0) + lua_pushfstring(L, "cannot %s %s: %s", what, filename, strerror(err)); + else + lua_pushfstring(L, "cannot %s %s", what, filename); lua_remove(L, fnameindex); return LUA_ERRFILE; } -static int skipBOM (LoadF *lf) { - const char *p = "\xEF\xBB\xBF"; /* Utf8 BOM mark */ - int c; - lf->n = 0; - do { - c = getc(lf->f); - if (c == EOF || c != *(const unsigned char *)p++) return c; - lf->buff[lf->n++] = c; /* to be read by the parser */ - } while (*p != '\0'); - lf->n = 0; /* prefix matched; discard it */ - return getc(lf->f); /* return next character */ +/* +** Skip an optional BOM at the start of a stream. If there is an +** incomplete BOM (the first character is correct but the rest is +** not), returns the first character anyway to force an error +** (as no chunk can start with 0xEF). +*/ +static int skipBOM (FILE *f) { + int c = getc(f); /* read first character */ + if (c == 0xEF && getc(f) == 0xBB && getc(f) == 0xBF) /* correct BOM? */ + return getc(f); /* ignore BOM and return next char */ + else /* no (valid) BOM */ + return c; /* return first character */ } @@ -625,13 +768,13 @@ static int skipBOM (LoadF *lf) { ** first "valid" character of the file (after the optional BOM and ** a first-line comment). */ -static int skipcomment (LoadF *lf, int *cp) { - int c = *cp = skipBOM(lf); +static int skipcomment (FILE *f, int *cp) { + int c = *cp = skipBOM(f); if (c == '#') { /* first line is a comment (Unix exec. file)? */ do { /* skip first line */ - c = getc(lf->f); - } while (c != EOF && c != '\n') ; - *cp = getc(lf->f); /* skip end-of-line, if present */ + c = getc(f); + } while (c != EOF && c != '\n'); + *cp = getc(f); /* next character after comment, if present */ return 1; /* there was a comment */ } else return 0; /* no comment */ @@ -650,18 +793,25 @@ LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, } else { lua_pushfstring(L, "@%s", filename); + errno = 0; lf.f = fopen(filename, "r"); if (lf.f == NULL) return errfile(L, "open", fnameindex); } - if (skipcomment(&lf, &c)) /* read initial portion */ - lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */ - if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */ - lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ - if (lf.f == NULL) return errfile(L, "reopen", fnameindex); - skipcomment(&lf, &c); /* re-read initial portion */ + lf.n = 0; + if (skipcomment(lf.f, &c)) /* read initial portion */ + lf.buff[lf.n++] = '\n'; /* add newline to correct line numbers */ + if (c == LUA_SIGNATURE[0]) { /* binary file? */ + lf.n = 0; /* remove possible newline */ + if (filename) { /* "real" file? */ + errno = 0; + lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ + if (lf.f == NULL) return errfile(L, "reopen", fnameindex); + skipcomment(lf.f, &c); /* re-read initial portion */ + } } if (c != EOF) lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */ + errno = 0; status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode); readstatus = ferror(lf.f); if (filename) fclose(lf.f); /* close file (even in case of errors) */ @@ -738,7 +888,7 @@ LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) { int isnum; lua_len(L, idx); l = lua_tointegerx(L, -1, &isnum); - if (!isnum) + if (l_unlikely(!isnum)) luaL_error(L, "object length is not an integer"); lua_pop(L, 1); /* remove object */ return l; @@ -746,13 +896,18 @@ LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) { LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { - if (!luaL_callmeta(L, idx, "__tostring")) { /* no metafield? */ + idx = lua_absindex(L,idx); + if (luaL_callmeta(L, idx, "__tostring")) { /* metafield? */ + if (!lua_isstring(L, -1)) + luaL_error(L, "'__tostring' must return a string"); + } + else { switch (lua_type(L, idx)) { case LUA_TNUMBER: { if (lua_isinteger(L, idx)) - lua_pushfstring(L, "%I", lua_tointeger(L, idx)); + lua_pushfstring(L, "%I", (LUAI_UACINT)lua_tointeger(L, idx)); else - lua_pushfstring(L, "%f", lua_tonumber(L, idx)); + lua_pushfstring(L, "%f", (LUAI_UACNUMBER)lua_tonumber(L, idx)); break; } case LUA_TSTRING: @@ -764,97 +919,21 @@ LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { case LUA_TNIL: lua_pushliteral(L, "nil"); break; - default: - lua_pushfstring(L, "%s: %p", luaL_typename(L, idx), - lua_topointer(L, idx)); + default: { + int tt = luaL_getmetafield(L, idx, "__name"); /* try name */ + const char *kind = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : + luaL_typename(L, idx); + lua_pushfstring(L, "%s: %p", kind, lua_topointer(L, idx)); + if (tt != LUA_TNIL) + lua_remove(L, -2); /* remove '__name' */ break; + } } } return lua_tolstring(L, -1, len); } -/* -** {====================================================== -** Compatibility with 5.1 module functions -** ======================================================= -*/ -#if defined(LUA_COMPAT_MODULE) - -static const char *luaL_findtable (lua_State *L, int idx, - const char *fname, int szhint) { - const char *e; - if (idx) lua_pushvalue(L, idx); - do { - e = strchr(fname, '.'); - if (e == NULL) e = fname + strlen(fname); - lua_pushlstring(L, fname, e - fname); - if (lua_rawget(L, -2) == LUA_TNIL) { /* no such field? */ - lua_pop(L, 1); /* remove this nil */ - lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ - lua_pushlstring(L, fname, e - fname); - lua_pushvalue(L, -2); - lua_settable(L, -4); /* set new table into field */ - } - else if (!lua_istable(L, -1)) { /* field has a non-table value? */ - lua_pop(L, 2); /* remove table and value */ - return fname; /* return problematic part of the name */ - } - lua_remove(L, -2); /* remove previous table */ - fname = e + 1; - } while (*e == '.'); - return NULL; -} - - -/* -** Count number of elements in a luaL_Reg list. -*/ -static int libsize (const luaL_Reg *l) { - int size = 0; - for (; l && l->name; l++) size++; - return size; -} - - -/* -** Find or create a module table with a given name. The function -** first looks at the _LOADED table and, if that fails, try a -** global variable with that name. In any case, leaves on the stack -** the module table. -*/ -LUALIB_API void luaL_pushmodule (lua_State *L, const char *modname, - int sizehint) { - luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); /* get _LOADED table */ - if (lua_getfield(L, -1, modname) != LUA_TTABLE) { /* no _LOADED[modname]? */ - lua_pop(L, 1); /* remove previous result */ - /* try global variable (and create one if it does not exist) */ - lua_pushglobaltable(L); - if (luaL_findtable(L, 0, modname, sizehint) != NULL) - luaL_error(L, "name conflict for module '%s'", modname); - lua_pushvalue(L, -1); - lua_setfield(L, -3, modname); /* _LOADED[modname] = new table */ - } - lua_remove(L, -2); /* remove _LOADED table */ -} - - -LUALIB_API void luaL_openlib (lua_State *L, const char *libname, - const luaL_Reg *l, int nup) { - luaL_checkversion(L); - if (libname) { - luaL_pushmodule(L, libname, libsize(l)); /* get/create library table */ - lua_insert(L, -(nup + 1)); /* move library table to below upvalues */ - } - if (l) - luaL_setfuncs(L, l, nup); - else - lua_pop(L, nup); /* remove upvalues */ -} - -#endif -/* }====================================================== */ - /* ** set functions from list 'l' into table at top - 'nup'; each ** function gets the 'nup' elements at the top as upvalues. @@ -863,10 +942,14 @@ LUALIB_API void luaL_openlib (lua_State *L, const char *libname, LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ - int i; - for (i = 0; i < nup; i++) /* copy upvalues to the top */ - lua_pushvalue(L, -nup); - lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + if (l->func == NULL) /* placeholder? */ + lua_pushboolean(L, 0); + else { + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + } lua_setfield(L, -(nup + 2), l->name); } lua_pop(L, nup); /* remove upvalues */ @@ -899,17 +982,17 @@ LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { */ LUALIB_API void luaL_requiref (lua_State *L, const char *modname, lua_CFunction openf, int glb) { - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_getfield(L, -1, modname); /* _LOADED[modname] */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, -1, modname); /* LOADED[modname] */ if (!lua_toboolean(L, -1)) { /* package not already loaded? */ lua_pop(L, 1); /* remove field */ lua_pushcfunction(L, openf); lua_pushstring(L, modname); /* argument to open function */ lua_call(L, 1, 1); /* call 'openf' to open module */ lua_pushvalue(L, -1); /* make copy of module (call result) */ - lua_setfield(L, -3, modname); /* _LOADED[modname] = module */ + lua_setfield(L, -3, modname); /* LOADED[modname] = module */ } - lua_remove(L, -2); /* remove _LOADED table */ + lua_remove(L, -2); /* remove LOADED table */ if (glb) { lua_pushvalue(L, -1); /* copy of module */ lua_setglobal(L, modname); /* _G[modname] = module */ @@ -917,18 +1000,24 @@ LUALIB_API void luaL_requiref (lua_State *L, const char *modname, } -LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p, - const char *r) { +LUALIB_API void luaL_addgsub (luaL_Buffer *b, const char *s, + const char *p, const char *r) { const char *wild; size_t l = strlen(p); - luaL_Buffer b; - luaL_buffinit(L, &b); while ((wild = strstr(s, p)) != NULL) { - luaL_addlstring(&b, s, wild - s); /* push prefix */ - luaL_addstring(&b, r); /* push replacement in place of pattern */ + luaL_addlstring(b, s, wild - s); /* push prefix */ + luaL_addstring(b, r); /* push replacement in place of pattern */ s = wild + l; /* continue after 'p' */ } - luaL_addstring(&b, s); /* push last suffix */ + luaL_addstring(b, s); /* push last suffix */ +} + + +LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, + const char *p, const char *r) { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addgsub(&b, s, p, r); luaL_pushresult(&b); return lua_tostring(L, -1); } @@ -945,28 +1034,93 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { } +/* +** Standard panic funcion just prints an error message. The test +** with 'lua_type' avoids possible memory errors in 'lua_tostring'. +*/ static int panic (lua_State *L) { + const char *msg = (lua_type(L, -1) == LUA_TSTRING) + ? lua_tostring(L, -1) + : "error object is not a string"; lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", - lua_tostring(L, -1)); + msg); return 0; /* return to Lua to abort */ } +/* +** Warning functions: +** warnfoff: warning system is off +** warnfon: ready to start a new message +** warnfcont: previous message is to be continued +*/ +static void warnfoff (void *ud, const char *message, int tocont); +static void warnfon (void *ud, const char *message, int tocont); +static void warnfcont (void *ud, const char *message, int tocont); + + +/* +** Check whether message is a control message. If so, execute the +** control or ignore it if unknown. +*/ +static int checkcontrol (lua_State *L, const char *message, int tocont) { + if (tocont || *(message++) != '@') /* not a control message? */ + return 0; + else { + if (strcmp(message, "off") == 0) + lua_setwarnf(L, warnfoff, L); /* turn warnings off */ + else if (strcmp(message, "on") == 0) + lua_setwarnf(L, warnfon, L); /* turn warnings on */ + return 1; /* it was a control message */ + } +} + + +static void warnfoff (void *ud, const char *message, int tocont) { + checkcontrol((lua_State *)ud, message, tocont); +} + + +/* +** Writes the message and handle 'tocont', finishing the message +** if needed and setting the next warn function. +*/ +static void warnfcont (void *ud, const char *message, int tocont) { + lua_State *L = (lua_State *)ud; + lua_writestringerror("%s", message); /* write message */ + if (tocont) /* not the last part? */ + lua_setwarnf(L, warnfcont, L); /* to be continued */ + else { /* last part */ + lua_writestringerror("%s", "\n"); /* finish message with end-of-line */ + lua_setwarnf(L, warnfon, L); /* next call is a new message */ + } +} + + +static void warnfon (void *ud, const char *message, int tocont) { + if (checkcontrol((lua_State *)ud, message, tocont)) /* control message? */ + return; /* nothing else to be done */ + lua_writestringerror("%s", "Lua warning: "); /* start a new warning */ + warnfcont(ud, message, tocont); /* finish processing */ +} + + LUALIB_API lua_State *luaL_newstate (void) { lua_State *L = lua_newstate(l_alloc, NULL); - if (L) lua_atpanic(L, &panic); + if (l_likely(L)) { + lua_atpanic(L, &panic); + lua_setwarnf(L, warnfoff, L); /* default is warnings off */ + } return L; } LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) { - const lua_Number *v = lua_version(L); + lua_Number v = lua_version(L); if (sz != LUAL_NUMSIZES) /* check numeric types */ luaL_error(L, "core and library have incompatible numeric types"); - if (v != lua_version(NULL)) - luaL_error(L, "multiple Lua VMs detected"); - else if (*v != ver) + else if (v != ver) luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f", - ver, *v); + (LUAI_UACNUMBER)ver, (LUAI_UACNUMBER)v); } diff --git a/libs/lua/lauxlib.h b/libs/lua/lauxlib.h index 0bac2467..5b977e2a 100644 --- a/libs/lua/lauxlib.h +++ b/libs/lua/lauxlib.h @@ -1,5 +1,5 @@ /* -** $Id: lauxlib.h,v 1.128 2014/10/29 16:11:17 roberto Exp $ +** $Id: lauxlib.h $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ @@ -12,14 +12,29 @@ #include #include +#include "luaconf.h" #include "lua.h" +/* global table */ +#define LUA_GNAME "_G" -/* extra error code for 'luaL_load' */ + +typedef struct luaL_Buffer luaL_Buffer; + + +/* extra error code for 'luaL_loadfilex' */ #define LUA_ERRFILE (LUA_ERRERR+1) +/* key, in the registry, for table of loaded modules */ +#define LUA_LOADED_TABLE "_LOADED" + + +/* key, in the registry, for table of preloaded loaders */ +#define LUA_PRELOAD_TABLE "_PRELOAD" + + typedef struct luaL_Reg { const char *name; lua_CFunction func; @@ -36,6 +51,7 @@ LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname); LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, size_t *l); LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, @@ -65,7 +81,8 @@ LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); LUALIB_API int (luaL_execresult) (lua_State *L, int stat); -/* pre-defined references */ + +/* predefined references */ #define LUA_NOREF (-2) #define LUA_REFNIL (-1) @@ -85,8 +102,10 @@ LUALIB_API lua_State *(luaL_newstate) (void); LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); -LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, - const char *r); +LUALIB_API void (luaL_addgsub) (luaL_Buffer *b, const char *s, + const char *p, const char *r); +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, + const char *p, const char *r); LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); @@ -112,7 +131,11 @@ LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) #define luaL_argcheck(L, cond,arg,extramsg) \ - ((void)((cond) || luaL_argerror(L, (arg), (extramsg)))) + ((void)(luai_likely(cond) || luaL_argerror(L, (arg), (extramsg)))) + +#define luaL_argexpected(L,cond,arg,tname) \ + ((void)(luai_likely(cond) || luaL_typeerror(L, (arg), (tname)))) + #define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) #define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) @@ -131,19 +154,54 @@ LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, #define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) +/* +** Perform arithmetic operations on lua_Integer values with wrap-around +** semantics, as the Lua core does. +*/ +#define luaL_intop(op,v1,v2) \ + ((lua_Integer)((lua_Unsigned)(v1) op (lua_Unsigned)(v2))) + + +/* push the value used to represent failure/error */ +#define luaL_pushfail(L) lua_pushnil(L) + + +/* +** Internal assertions for in-house debugging +*/ +#if !defined(lua_assert) + +#if defined LUAI_ASSERT + #include + #define lua_assert(c) assert(c) +#else + #define lua_assert(c) ((void)0) +#endif + +#endif + + + /* ** {====================================================== ** Generic Buffer manipulation ** ======================================================= */ -typedef struct luaL_Buffer { +struct luaL_Buffer { char *b; /* buffer address */ size_t size; /* buffer size */ size_t n; /* number of characters in buffer */ lua_State *L; - char initb[LUAL_BUFFERSIZE]; /* initial buffer */ -} luaL_Buffer; + union { + LUAI_MAXALIGN; /* ensure maximum alignment for buffer */ + char b[LUAL_BUFFERSIZE]; /* initial buffer */ + } init; +}; + + +#define luaL_bufflen(bf) ((bf)->n) +#define luaL_buffaddr(bf) ((bf)->b) #define luaL_addchar(B,c) \ @@ -152,6 +210,8 @@ typedef struct luaL_Buffer { #define luaL_addsize(B,s) ((B)->n += (s)) +#define luaL_buffsub(B,s) ((B)->n -= (s)) + LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); @@ -189,21 +249,6 @@ typedef struct luaL_Stream { /* }====================================================== */ - - -/* compatibility with old module system */ -#if defined(LUA_COMPAT_MODULE) - -LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname, - int sizehint); -LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, - const luaL_Reg *l, int nup); - -#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0)) - -#endif - - /* ** {================================================================== ** "Abstraction Layer" for basic report of messages and errors diff --git a/libs/lua/lbaselib.c b/libs/lua/lbaselib.c index 9a151245..1d60c9de 100644 --- a/libs/lua/lbaselib.c +++ b/libs/lua/lbaselib.c @@ -1,5 +1,5 @@ /* -** $Id: lbaselib.c,v 1.310 2015/03/28 19:14:47 roberto Exp $ +** $Id: lbaselib.c $ ** Basic library ** See Copyright Notice in lua.h */ @@ -24,18 +24,12 @@ static int luaB_print (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ int i; - lua_getglobal(L, "tostring"); - for (i=1; i<=n; i++) { - const char *s; + for (i = 1; i <= n; i++) { /* for each argument */ size_t l; - lua_pushvalue(L, -1); /* function to be called */ - lua_pushvalue(L, i); /* value to print */ - lua_call(L, 1, 1); - s = lua_tolstring(L, -1, &l); /* get result */ - if (s == NULL) - return luaL_error(L, "'tostring' must return a string to 'print'"); - if (i>1) lua_writestring("\t", 1); - lua_writestring(s, l); + const char *s = luaL_tolstring(L, i, &l); /* convert it to string */ + if (i > 1) /* not the first element? */ + lua_writestring("\t", 1); /* add a tab before it */ + lua_writestring(s, l); /* print it */ lua_pop(L, 1); /* pop result */ } lua_writeline(); @@ -43,13 +37,31 @@ static int luaB_print (lua_State *L) { } +/* +** Creates a warning with all given arguments. +** Check first for errors; otherwise an error may interrupt +** the composition of a warning, leaving it unfinished. +*/ +static int luaB_warn (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + luaL_checkstring(L, 1); /* at least one argument */ + for (i = 2; i <= n; i++) + luaL_checkstring(L, i); /* make sure all arguments are strings */ + for (i = 1; i < n; i++) /* compose warning */ + lua_warning(L, lua_tostring(L, i), 1); + lua_warning(L, lua_tostring(L, n), 0); /* close warning */ + return 0; +} + + #define SPACECHARS " \f\n\r\t\v" static const char *b_str2int (const char *s, int base, lua_Integer *pn) { lua_Unsigned n = 0; int neg = 0; s += strspn(s, SPACECHARS); /* skip initial spaces */ - if (*s == '-') { s++; neg = 1; } /* handle signal */ + if (*s == '-') { s++; neg = 1; } /* handle sign */ else if (*s == '+') s++; if (!isalnum((unsigned char)*s)) /* no digit? */ return NULL; @@ -68,7 +80,6 @@ static const char *b_str2int (const char *s, int base, lua_Integer *pn) { static int luaB_tonumber (lua_State *L) { if (lua_isnoneornil(L, 2)) { /* standard conversion? */ - luaL_checkany(L, 1); if (lua_type(L, 1) == LUA_TNUMBER) { /* already a number? */ lua_settop(L, 1); /* yes; return it */ return 1; @@ -79,6 +90,7 @@ static int luaB_tonumber (lua_State *L) { if (s != NULL && lua_stringtonumber(L, s) == l + 1) return 1; /* successful conversion to number */ /* else not a number */ + luaL_checkany(L, 1); /* (but there must be some parameter) */ } } else { @@ -86,15 +98,15 @@ static int luaB_tonumber (lua_State *L) { const char *s; lua_Integer n = 0; /* to avoid warnings */ lua_Integer base = luaL_checkinteger(L, 2); - luaL_checktype(L, 1, LUA_TSTRING); /* before 'luaL_checklstring'! */ - s = luaL_checklstring(L, 1, &l); + luaL_checktype(L, 1, LUA_TSTRING); /* no numbers as strings */ + s = lua_tolstring(L, 1, &l); luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); if (b_str2int(s, (int)base, &n) == s + l) { lua_pushinteger(L, n); return 1; } /* else not a number */ } /* else not a number */ - lua_pushnil(L); /* not a number */ + luaL_pushfail(L); /* not a number */ return 1; } @@ -102,8 +114,8 @@ static int luaB_tonumber (lua_State *L) { static int luaB_error (lua_State *L) { int level = (int)luaL_optinteger(L, 2, 1); lua_settop(L, 1); - if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ - luaL_where(L, level); + if (lua_type(L, 1) == LUA_TSTRING && level > 0) { + luaL_where(L, level); /* add extra information */ lua_pushvalue(L, 1); lua_concat(L, 2); } @@ -125,9 +137,8 @@ static int luaB_getmetatable (lua_State *L) { static int luaB_setmetatable (lua_State *L) { int t = lua_type(L, 2); luaL_checktype(L, 1, LUA_TTABLE); - luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, - "nil or table expected"); - if (luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL) + luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table"); + if (l_unlikely(luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL)) return luaL_error(L, "cannot change a protected metatable"); lua_settop(L, 2); lua_setmetatable(L, 1); @@ -145,8 +156,8 @@ static int luaB_rawequal (lua_State *L) { static int luaB_rawlen (lua_State *L) { int t = lua_type(L, 1); - luaL_argcheck(L, t == LUA_TTABLE || t == LUA_TSTRING, 1, - "table or string expected"); + luaL_argexpected(L, t == LUA_TTABLE || t == LUA_TSTRING, 1, + "table or string"); lua_pushinteger(L, lua_rawlen(L, 1)); return 1; } @@ -170,61 +181,89 @@ static int luaB_rawset (lua_State *L) { } +static int pushmode (lua_State *L, int oldmode) { + if (oldmode == -1) + luaL_pushfail(L); /* invalid call to 'lua_gc' */ + else + lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental" + : "generational"); + return 1; +} + + +/* +** check whether call to 'lua_gc' was valid (not inside a finalizer) +*/ +#define checkvalres(res) { if (res == -1) break; } + static int luaB_collectgarbage (lua_State *L) { static const char *const opts[] = {"stop", "restart", "collect", "count", "step", "setpause", "setstepmul", - "isrunning", NULL}; + "isrunning", "generational", "incremental", NULL}; static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL, - LUA_GCISRUNNING}; + LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC}; int o = optsnum[luaL_checkoption(L, 1, "collect", opts)]; - int ex = (int)luaL_optinteger(L, 2, 0); - int res = lua_gc(L, o, ex); switch (o) { case LUA_GCCOUNT: { - int b = lua_gc(L, LUA_GCCOUNTB, 0); - lua_pushnumber(L, (lua_Number)res + ((lua_Number)b/1024)); + int k = lua_gc(L, o); + int b = lua_gc(L, LUA_GCCOUNTB); + checkvalres(k); + lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024)); + return 1; + } + case LUA_GCSTEP: { + int step = (int)luaL_optinteger(L, 2, 0); + int res = lua_gc(L, o, step); + checkvalres(res); + lua_pushboolean(L, res); + return 1; + } + case LUA_GCSETPAUSE: + case LUA_GCSETSTEPMUL: { + int p = (int)luaL_optinteger(L, 2, 0); + int previous = lua_gc(L, o, p); + checkvalres(previous); + lua_pushinteger(L, previous); return 1; } - case LUA_GCSTEP: case LUA_GCISRUNNING: { + case LUA_GCISRUNNING: { + int res = lua_gc(L, o); + checkvalres(res); lua_pushboolean(L, res); return 1; } + case LUA_GCGEN: { + int minormul = (int)luaL_optinteger(L, 2, 0); + int majormul = (int)luaL_optinteger(L, 3, 0); + return pushmode(L, lua_gc(L, o, minormul, majormul)); + } + case LUA_GCINC: { + int pause = (int)luaL_optinteger(L, 2, 0); + int stepmul = (int)luaL_optinteger(L, 3, 0); + int stepsize = (int)luaL_optinteger(L, 4, 0); + return pushmode(L, lua_gc(L, o, pause, stepmul, stepsize)); + } default: { + int res = lua_gc(L, o); + checkvalres(res); lua_pushinteger(L, res); return 1; } } + luaL_pushfail(L); /* invalid call (inside a finalizer) */ + return 1; } -/* -** This function has all type names as upvalues, to maximize performance. -*/ static int luaB_type (lua_State *L) { - luaL_checkany(L, 1); - lua_pushvalue(L, lua_upvalueindex(lua_type(L, 1) + 1)); + int t = lua_type(L, 1); + luaL_argcheck(L, t != LUA_TNONE, 1, "value expected"); + lua_pushstring(L, lua_typename(L, t)); return 1; } -static int pairsmeta (lua_State *L, const char *method, int iszero, - lua_CFunction iter) { - if (luaL_getmetafield(L, 1, method) == LUA_TNIL) { /* no metamethod? */ - luaL_checktype(L, 1, LUA_TTABLE); /* argument must be a table */ - lua_pushcfunction(L, iter); /* will return generator, */ - lua_pushvalue(L, 1); /* state, */ - if (iszero) lua_pushinteger(L, 0); /* and initial value */ - else lua_pushnil(L); - } - else { - lua_pushvalue(L, 1); /* argument 'self' to metamethod */ - lua_call(L, 1, 3); /* get 3 values from metamethod */ - } - return 3; -} - - static int luaB_next (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 2); /* create a 2nd argument if there isn't one */ @@ -237,54 +276,52 @@ static int luaB_next (lua_State *L) { } -static int luaB_pairs (lua_State *L) { - return pairsmeta(L, "__pairs", 0, luaB_next); +static int pairscont (lua_State *L, int status, lua_KContext k) { + (void)L; (void)status; (void)k; /* unused */ + return 3; } - -/* -** Traversal function for 'ipairs' for raw tables -*/ -static int ipairsaux_raw (lua_State *L) { - lua_Integer i = luaL_checkinteger(L, 2) + 1; - luaL_checktype(L, 1, LUA_TTABLE); - lua_pushinteger(L, i); - return (lua_rawgeti(L, 1, i) == LUA_TNIL) ? 1 : 2; +static int luaB_pairs (lua_State *L) { + luaL_checkany(L, 1); + if (luaL_getmetafield(L, 1, "__pairs") == LUA_TNIL) { /* no metamethod? */ + lua_pushcfunction(L, luaB_next); /* will return generator, */ + lua_pushvalue(L, 1); /* state, */ + lua_pushnil(L); /* and initial value */ + } + else { + lua_pushvalue(L, 1); /* argument 'self' to metamethod */ + lua_callk(L, 1, 3, 0, pairscont); /* get 3 values from metamethod */ + } + return 3; } /* -** Traversal function for 'ipairs' for tables with metamethods +** Traversal function for 'ipairs' */ static int ipairsaux (lua_State *L) { - lua_Integer i = luaL_checkinteger(L, 2) + 1; + lua_Integer i = luaL_checkinteger(L, 2); + i = luaL_intop(+, i, 1); lua_pushinteger(L, i); return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; } /* -** This function will use either 'ipairsaux' or 'ipairsaux_raw' to -** traverse a table, depending on whether the table has metamethods -** that can affect the traversal. +** 'ipairs' function. Returns 'ipairsaux', given "table", 0. +** (The given "table" may not be a table.) */ static int luaB_ipairs (lua_State *L) { - lua_CFunction iter = (luaL_getmetafield(L, 1, "__index") != LUA_TNIL) - ? ipairsaux : ipairsaux_raw; -#if defined(LUA_COMPAT_IPAIRS) - return pairsmeta(L, "__ipairs", 1, iter); -#else luaL_checkany(L, 1); - lua_pushcfunction(L, iter); /* iteration function */ + lua_pushcfunction(L, ipairsaux); /* iteration function */ lua_pushvalue(L, 1); /* state */ lua_pushinteger(L, 0); /* initial value */ return 3; -#endif } static int load_aux (lua_State *L, int status, int envidx) { - if (status == LUA_OK) { + if (l_likely(status == LUA_OK)) { if (envidx != 0) { /* 'env' parameter? */ lua_pushvalue(L, envidx); /* environment for loaded function */ if (!lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */ @@ -293,9 +330,9 @@ static int load_aux (lua_State *L, int status, int envidx) { return 1; } else { /* error (message is on top of the stack) */ - lua_pushnil(L); + luaL_pushfail(L); lua_insert(L, -2); /* put before error message */ - return 2; /* return nil plus error message */ + return 2; /* return fail plus error message */ } } @@ -340,7 +377,7 @@ static const char *generic_reader (lua_State *L, void *ud, size_t *size) { *size = 0; return NULL; } - else if (!lua_isstring(L, -1)) + else if (l_unlikely(!lua_isstring(L, -1))) luaL_error(L, "reader function must return a string"); lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */ return lua_tolstring(L, RESERVEDSLOT, size); @@ -378,7 +415,7 @@ static int dofilecont (lua_State *L, int d1, lua_KContext d2) { static int luaB_dofile (lua_State *L) { const char *fname = luaL_optstring(L, 1, NULL); lua_settop(L, 1); - if (luaL_loadfile(L, fname) != LUA_OK) + if (l_unlikely(luaL_loadfile(L, fname) != LUA_OK)) return lua_error(L); lua_callk(L, 0, LUA_MULTRET, 0, dofilecont); return dofilecont(L, 0, 0); @@ -386,7 +423,7 @@ static int luaB_dofile (lua_State *L) { static int luaB_assert (lua_State *L) { - if (lua_toboolean(L, 1)) /* condition is true? */ + if (l_likely(lua_toboolean(L, 1))) /* condition is true? */ return lua_gettop(L); /* return all arguments */ else { /* error */ luaL_checkany(L, 1); /* there must be a condition */ @@ -422,7 +459,7 @@ static int luaB_select (lua_State *L) { ** ignored). */ static int finishpcall (lua_State *L, int status, lua_KContext extra) { - if (status != LUA_OK && status != LUA_YIELD) { /* error? */ + if (l_unlikely(status != LUA_OK && status != LUA_YIELD)) { /* error? */ lua_pushboolean(L, 0); /* first result (false) */ lua_pushvalue(L, -2); /* error message */ return 2; /* return false, msg */ @@ -475,13 +512,11 @@ static const luaL_Reg base_funcs[] = { {"ipairs", luaB_ipairs}, {"loadfile", luaB_loadfile}, {"load", luaB_load}, -#if defined(LUA_COMPAT_LOADSTRING) - {"loadstring", luaB_load}, -#endif {"next", luaB_next}, {"pairs", luaB_pairs}, {"pcall", luaB_pcall}, {"print", luaB_print}, + {"warn", luaB_warn}, {"rawequal", luaB_rawequal}, {"rawlen", luaB_rawlen}, {"rawget", luaB_rawget}, @@ -490,31 +525,25 @@ static const luaL_Reg base_funcs[] = { {"setmetatable", luaB_setmetatable}, {"tonumber", luaB_tonumber}, {"tostring", luaB_tostring}, + {"type", luaB_type}, {"xpcall", luaB_xpcall}, /* placeholders */ - {"type", NULL}, - {"_G", NULL}, + {LUA_GNAME, NULL}, {"_VERSION", NULL}, {NULL, NULL} }; LUAMOD_API int luaopen_base (lua_State *L) { - int i; /* open lib into global table */ lua_pushglobaltable(L); luaL_setfuncs(L, base_funcs, 0); /* set global _G */ lua_pushvalue(L, -1); - lua_setfield(L, -2, "_G"); + lua_setfield(L, -2, LUA_GNAME); /* set global _VERSION */ lua_pushliteral(L, LUA_VERSION); lua_setfield(L, -2, "_VERSION"); - /* set function 'type' with proper upvalues */ - for (i = 0; i < LUA_NUMTAGS; i++) /* push all type names as upvalues */ - lua_pushstring(L, lua_typename(L, i)); - lua_pushcclosure(L, luaB_type, LUA_NUMTAGS); - lua_setfield(L, -2, "type"); return 1; } diff --git a/libs/lua/lcode.c b/libs/lua/lcode.c index d6f0fcd8..87616140 100644 --- a/libs/lua/lcode.c +++ b/libs/lua/lcode.c @@ -1,5 +1,5 @@ /* -** $Id: lcode.c,v 2.101 2015/04/29 18:24:11 roberto Exp $ +** $Id: lcode.c $ ** Code generator for Lua ** See Copyright Notice in lua.h */ @@ -10,6 +10,8 @@ #include "lprefix.h" +#include +#include #include #include @@ -36,8 +38,23 @@ #define hasjumps(e) ((e)->t != (e)->f) -static int tonumeral(expdesc *e, TValue *v) { - if (e->t != NO_JUMP || e->f != NO_JUMP) +static int codesJ (FuncState *fs, OpCode o, int sj, int k); + + + +/* semantic error */ +l_noret luaK_semerror (LexState *ls, const char *msg) { + ls->t.token = 0; /* remove "near " from final message */ + luaX_syntaxerror(ls, msg); +} + + +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +static int tonumeral (const expdesc *e, TValue *v) { + if (hasjumps(e)) return 0; /* not a numeral */ switch (e->k) { case VKINT: @@ -51,56 +68,160 @@ static int tonumeral(expdesc *e, TValue *v) { } -void luaK_nil (FuncState *fs, int from, int n) { - Instruction *previous; - int l = from + n - 1; /* last register to set nil */ - if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ - previous = &fs->f->code[fs->pc-1]; - if (GET_OPCODE(*previous) == OP_LOADNIL) { - int pfrom = GETARG_A(*previous); - int pl = pfrom + GETARG_B(*previous); - if ((pfrom <= from && from <= pl + 1) || - (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ - if (pfrom < from) from = pfrom; /* from = min(from, pfrom) */ - if (pl > l) l = pl; /* l = max(l, pl) */ - SETARG_A(*previous, from); - SETARG_B(*previous, l - from); - return; - } - } /* else go through */ +/* +** Get the constant value from a constant expression +*/ +static TValue *const2val (FuncState *fs, const expdesc *e) { + lua_assert(e->k == VCONST); + return &fs->ls->dyd->actvar.arr[e->u.info].k; +} + + +/* +** If expression is a constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v) { + if (hasjumps(e)) + return 0; /* not a constant */ + switch (e->k) { + case VFALSE: + setbfvalue(v); + return 1; + case VTRUE: + setbtvalue(v); + return 1; + case VNIL: + setnilvalue(v); + return 1; + case VKSTR: { + setsvalue(fs->ls->L, v, e->u.strval); + return 1; + } + case VCONST: { + setobj(fs->ls->L, v, const2val(fs, e)); + return 1; + } + default: return tonumeral(e, v); } - luaK_codeABC(fs, OP_LOADNIL, from, n - 1, 0); /* else no optimization */ } -int luaK_jump (FuncState *fs) { - int jpc = fs->jpc; /* save list of jumps to here */ - int j; - fs->jpc = NO_JUMP; - j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); - luaK_concat(fs, &j, jpc); /* keep them on hold */ - return j; +/* +** Return the previous instruction of the current code. If there +** may be a jump target between the current instruction and the +** previous one, return an invalid instruction (to avoid wrong +** optimizations). +*/ +static Instruction *previousinstruction (FuncState *fs) { + static const Instruction invalidinstruction = ~(Instruction)0; + if (fs->pc > fs->lasttarget) + return &fs->f->code[fs->pc - 1]; /* previous instruction */ + else + return cast(Instruction*, &invalidinstruction); } -void luaK_ret (FuncState *fs, int first, int nret) { - luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ +void luaK_nil (FuncState *fs, int from, int n) { + int l = from + n - 1; /* last register to set nil */ + Instruction *previous = previousinstruction(fs); + if (GET_OPCODE(*previous) == OP_LOADNIL) { /* previous is LOADNIL? */ + int pfrom = GETARG_A(*previous); /* get previous range */ + int pl = pfrom + GETARG_B(*previous); + if ((pfrom <= from && from <= pl + 1) || + (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ + if (pfrom < from) from = pfrom; /* from = min(from, pfrom) */ + if (pl > l) l = pl; /* l = max(l, pl) */ + SETARG_A(*previous, from); + SETARG_B(*previous, l - from); + return; + } /* else go through */ + } + luaK_codeABC(fs, OP_LOADNIL, from, n - 1, 0); /* else no optimization */ } -static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { - luaK_codeABC(fs, op, A, B, C); - return luaK_jump(fs); +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sJ(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ } +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ static void fixjump (FuncState *fs, int pc, int dest) { Instruction *jmp = &fs->f->code[pc]; - int offset = dest-(pc+1); + int offset = dest - (pc + 1); lua_assert(dest != NO_JUMP); - if (abs(offset) > MAXARG_sBx) + if (!(-OFFSET_sJ <= offset && offset <= MAXARG_sJ - OFFSET_sJ)) luaX_syntaxerror(fs->ls, "control structure too long"); - SETARG_sBx(*jmp, offset); + lua_assert(GET_OPCODE(*jmp) == OP_JMP); + SETARG_sJ(*jmp, offset); +} + + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; /* nothing to concatenate? */ + else if (*l1 == NO_JUMP) /* no original list? */ + *l1 = l2; /* 'l1' points to 'l2' */ + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); /* last element links to 'l2' */ + } +} + + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). +*/ +int luaK_jump (FuncState *fs) { + return codesJ(fs, OP_JMP, NO_JUMP, 0); +} + + +/* +** Code a 'return' instruction +*/ +void luaK_ret (FuncState *fs, int first, int nret) { + OpCode op; + switch (nret) { + case 0: op = OP_RETURN0; break; + case 1: op = OP_RETURN1; break; + default: op = OP_RETURN; break; + } + luaK_codeABC(fs, op, first, nret + 1, 0); +} + + +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ +static int condjump (FuncState *fs, OpCode op, int A, int B, int C, int k) { + luaK_codeABCk(fs, op, A, B, C, k); + return luaK_jump(fs); } @@ -114,15 +235,11 @@ int luaK_getlabel (FuncState *fs) { } -static int getjump (FuncState *fs, int pc) { - int offset = GETARG_sBx(fs->f->code[pc]); - if (offset == NO_JUMP) /* point to itself represents end of list */ - return NO_JUMP; /* end of list */ - else - return (pc+1)+offset; /* turn offset into absolute position */ -} - - +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ static Instruction *getjumpcontrol (FuncState *fs, int pc) { Instruction *pi = &fs->f->code[pc]; if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) @@ -133,37 +250,41 @@ static Instruction *getjumpcontrol (FuncState *fs, int pc) { /* -** check whether list has any jump that do not produce a value -** (or produce an inverted value) +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) */ -static int need_value (FuncState *fs, int list) { - for (; list != NO_JUMP; list = getjump(fs, list)) { - Instruction i = *getjumpcontrol(fs, list); - if (GET_OPCODE(i) != OP_TESTSET) return 1; - } - return 0; /* not found */ -} - - static int patchtestreg (FuncState *fs, int node, int reg) { Instruction *i = getjumpcontrol(fs, node); if (GET_OPCODE(*i) != OP_TESTSET) return 0; /* cannot patch other instructions */ if (reg != NO_REG && reg != GETARG_B(*i)) SETARG_A(*i, reg); - else /* no register to put value or register already has the value */ - *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); - + else { + /* no register to put value or register already has the value; + change instruction to simple test */ + *i = CREATE_ABCk(OP_TEST, GETARG_B(*i), 0, 0, GETARG_k(*i)); + } return 1; } +/* +** Traverse a list of tests ensuring no one produces a value +*/ static void removevalues (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) patchtestreg(fs, list, NO_REG); } +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, int dtarget) { while (list != NO_JUMP) { @@ -177,94 +298,157 @@ static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, } -static void dischargejpc (FuncState *fs) { - patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); - fs->jpc = NO_JUMP; +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ +void luaK_patchlist (FuncState *fs, int list, int target) { + lua_assert(target <= fs->pc); + patchlistaux(fs, list, target, NO_REG, target); } -void luaK_patchlist (FuncState *fs, int list, int target) { - if (target == fs->pc) - luaK_patchtohere(fs, list); - else { - lua_assert(target < fs->pc); - patchlistaux(fs, list, target, NO_REG, target); - } +void luaK_patchtohere (FuncState *fs, int list) { + int hr = luaK_getlabel(fs); /* mark "here" as a jump target */ + luaK_patchlist(fs, list, hr); } -void luaK_patchclose (FuncState *fs, int list, int level) { - level++; /* argument is +1 to reserve 0 as non-op */ - while (list != NO_JUMP) { - int next = getjump(fs, list); - lua_assert(GET_OPCODE(fs->f->code[list]) == OP_JMP && - (GETARG_A(fs->f->code[list]) == 0 || - GETARG_A(fs->f->code[list]) >= level)); - SETARG_A(fs->f->code[list], level); - list = next; +/* limit for difference between lines in relative line info. */ +#define LIMLINEDIFF 0x80 + + +/* +** Save line info for a new instruction. If difference from last line +** does not fit in a byte, of after that many instructions, save a new +** absolute line info; (in that case, the special value 'ABSLINEINFO' +** in 'lineinfo' signals the existence of this absolute information.) +** Otherwise, store the difference from last line in 'lineinfo'. +*/ +static void savelineinfo (FuncState *fs, Proto *f, int line) { + int linedif = line - fs->previousline; + int pc = fs->pc - 1; /* last instruction coded */ + if (abs(linedif) >= LIMLINEDIFF || fs->iwthabs++ >= MAXIWTHABS) { + luaM_growvector(fs->ls->L, f->abslineinfo, fs->nabslineinfo, + f->sizeabslineinfo, AbsLineInfo, MAX_INT, "lines"); + f->abslineinfo[fs->nabslineinfo].pc = pc; + f->abslineinfo[fs->nabslineinfo++].line = line; + linedif = ABSLINEINFO; /* signal that there is absolute information */ + fs->iwthabs = 1; /* restart counter */ } + luaM_growvector(fs->ls->L, f->lineinfo, pc, f->sizelineinfo, ls_byte, + MAX_INT, "opcodes"); + f->lineinfo[pc] = linedif; + fs->previousline = line; /* last line saved */ } -void luaK_patchtohere (FuncState *fs, int list) { - luaK_getlabel(fs); - luaK_concat(fs, &fs->jpc, list); +/* +** Remove line information from the last instruction. +** If line information for that instruction is absolute, set 'iwthabs' +** above its max to force the new (replacing) instruction to have +** absolute line info, too. +*/ +static void removelastlineinfo (FuncState *fs) { + Proto *f = fs->f; + int pc = fs->pc - 1; /* last instruction coded */ + if (f->lineinfo[pc] != ABSLINEINFO) { /* relative line info? */ + fs->previousline -= f->lineinfo[pc]; /* correct last line saved */ + fs->iwthabs--; /* undo previous increment */ + } + else { /* absolute line information */ + lua_assert(f->abslineinfo[fs->nabslineinfo - 1].pc == pc); + fs->nabslineinfo--; /* remove it */ + fs->iwthabs = MAXIWTHABS + 1; /* force next line info to be absolute */ + } } -void luaK_concat (FuncState *fs, int *l1, int l2) { - if (l2 == NO_JUMP) return; - else if (*l1 == NO_JUMP) - *l1 = l2; - else { - int list = *l1; - int next; - while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ - list = next; - fixjump(fs, list, l2); - } +/* +** Remove the last instruction created, correcting line information +** accordingly. +*/ +static void removelastinstruction (FuncState *fs) { + removelastlineinfo(fs); + fs->pc--; } -static int luaK_code (FuncState *fs, Instruction i) { +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ +int luaK_code (FuncState *fs, Instruction i) { Proto *f = fs->f; - dischargejpc(fs); /* 'pc' will change */ /* put new instruction in code array */ luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction, MAX_INT, "opcodes"); - f->code[fs->pc] = i; - /* save corresponding line information */ - luaM_growvector(fs->ls->L, f->lineinfo, fs->pc, f->sizelineinfo, int, - MAX_INT, "opcodes"); - f->lineinfo[fs->pc] = fs->ls->lastline; - return fs->pc++; + f->code[fs->pc++] = i; + savelineinfo(fs, f, fs->ls->lastline); + return fs->pc - 1; /* index of new instruction */ } -int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ +int luaK_codeABCk (FuncState *fs, OpCode o, int a, int b, int c, int k) { lua_assert(getOpMode(o) == iABC); - lua_assert(getBMode(o) != OpArgN || b == 0); - lua_assert(getCMode(o) != OpArgN || c == 0); - lua_assert(a <= MAXARG_A && b <= MAXARG_B && c <= MAXARG_C); - return luaK_code(fs, CREATE_ABC(o, a, b, c)); + lua_assert(a <= MAXARG_A && b <= MAXARG_B && + c <= MAXARG_C && (k & ~1) == 0); + return luaK_code(fs, CREATE_ABCk(o, a, b, c, k)); } +/* +** Format and emit an 'iABx' instruction. +*/ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { - lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); - lua_assert(getCMode(o) == OpArgN); + lua_assert(getOpMode(o) == iABx); lua_assert(a <= MAXARG_A && bc <= MAXARG_Bx); return luaK_code(fs, CREATE_ABx(o, a, bc)); } +/* +** Format and emit an 'iAsBx' instruction. +*/ +static int codeAsBx (FuncState *fs, OpCode o, int a, int bc) { + unsigned int b = bc + OFFSET_sBx; + lua_assert(getOpMode(o) == iAsBx); + lua_assert(a <= MAXARG_A && b <= MAXARG_Bx); + return luaK_code(fs, CREATE_ABx(o, a, b)); +} + + +/* +** Format and emit an 'isJ' instruction. +*/ +static int codesJ (FuncState *fs, OpCode o, int sj, int k) { + unsigned int j = sj + OFFSET_sJ; + lua_assert(getOpMode(o) == isJ); + lua_assert(j <= MAXARG_sJ && (k & ~1) == 0); + return luaK_code(fs, CREATE_sJ(o, j, k)); +} + + +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ static int codeextraarg (FuncState *fs, int a) { lua_assert(a <= MAXARG_Ax); return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a)); } -int luaK_codek (FuncState *fs, int reg, int k) { +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ +static int luaK_codek (FuncState *fs, int reg, int k) { if (k <= MAXARG_Bx) return luaK_codeABx(fs, OP_LOADK, reg, k); else { @@ -275,6 +459,10 @@ int luaK_codek (FuncState *fs, int reg, int k) { } +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ void luaK_checkstack (FuncState *fs, int n) { int newstack = fs->freereg + n; if (newstack > fs->f->maxstacksize) { @@ -286,20 +474,46 @@ void luaK_checkstack (FuncState *fs, int n) { } +/* +** Reserve 'n' registers in register stack +*/ void luaK_reserveregs (FuncState *fs, int n) { luaK_checkstack(fs, n); fs->freereg += n; } +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +) +*/ static void freereg (FuncState *fs, int reg) { - if (!ISK(reg) && reg >= fs->nactvar) { + if (reg >= luaY_nvarstack(fs)) { fs->freereg--; lua_assert(reg == fs->freereg); } } +/* +** Free two registers in proper order +*/ +static void freeregs (FuncState *fs, int r1, int r2) { + if (r1 > r2) { + freereg(fs, r1); + freereg(fs, r2); + } + else { + freereg(fs, r2); + freereg(fs, r1); + } +} + + +/* +** Free register used by expression 'e' (if any) +*/ static void freeexp (FuncState *fs, expdesc *e) { if (e->k == VNONRELOC) freereg(fs, e->u.info); @@ -307,18 +521,35 @@ static void freeexp (FuncState *fs, expdesc *e) { /* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { + int r1 = (e1->k == VNONRELOC) ? e1->u.info : -1; + int r2 = (e2->k == VNONRELOC) ? e2->u.info : -1; + freeregs(fs, r1, r2); +} + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). ** Use scanner's table to cache position of constants in constant list -** and try to reuse constants +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. +** Note that all functions share the same table, so entering or exiting +** a function can make some indices wrong. */ static int addk (FuncState *fs, TValue *key, TValue *v) { + TValue val; lua_State *L = fs->ls->L; Proto *f = fs->f; - TValue *idx = luaH_set(L, fs->ls->h, key); /* index scanner table */ + const TValue *idx = luaH_get(fs->ls->h, key); /* query scanner table */ int k, oldsize; if (ttisinteger(idx)) { /* is there an index there? */ k = cast_int(ivalue(idx)); /* correct value? (warning: must distinguish floats from integers!) */ - if (k < fs->nk && ttype(&f->k[k]) == ttype(v) && + if (k < fs->nk && ttypetag(&f->k[k]) == ttypetag(v) && luaV_rawequalobj(&f->k[k], v)) return k; /* reuse index */ } @@ -327,7 +558,8 @@ static int addk (FuncState *fs, TValue *key, TValue *v) { k = fs->nk; /* numerical value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ - setivalue(idx, k); + setivalue(&val, k); + luaH_finishset(L, fs->ls->h, key, idx, &val); luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); setobj(L, &f->k[k], v); @@ -337,40 +569,79 @@ static int addk (FuncState *fs, TValue *key, TValue *v) { } -int luaK_stringK (FuncState *fs, TString *s) { +/* +** Add a string to list of constants and return its index. +*/ +static int stringK (FuncState *fs, TString *s) { TValue o; setsvalue(fs->ls->L, &o, s); - return addk(fs, &o, &o); + return addk(fs, &o, &o); /* use string itself as key */ } /* -** Integers use userdata as keys to avoid collision with floats with same -** value; conversion to 'void*' used only for hashing, no "precision" -** problems +** Add an integer to list of constants and return its index. */ -int luaK_intK (FuncState *fs, lua_Integer n) { - TValue k, o; - setpvalue(&k, cast(void*, cast(size_t, n))); +static int luaK_intK (FuncState *fs, lua_Integer n) { + TValue o; setivalue(&o, n); - return addk(fs, &k, &o); + return addk(fs, &o, &o); /* use integer itself as key */ } - +/* +** Add a float to list of constants and return its index. Floats +** with integral values need a different key, to avoid collision +** with actual integers. To that, we add to the number its smaller +** power-of-two fraction that is still significant in its scale. +** For doubles, that would be 1/2^52. +** (This method is not bulletproof: there may be another float +** with that value, and for floats larger than 2^53 the result is +** still an integer. At worst, this only wastes an entry with +** a duplicate.) +*/ static int luaK_numberK (FuncState *fs, lua_Number r) { TValue o; + lua_Integer ik; setfltvalue(&o, r); - return addk(fs, &o, &o); + if (!luaV_flttointeger(r, &ik, F2Ieq)) /* not an integral value? */ + return addk(fs, &o, &o); /* use number itself as key */ + else { /* must build an alternative key */ + const int nbm = l_floatatt(MANT_DIG); + const lua_Number q = l_mathop(ldexp)(l_mathop(1.0), -nbm + 1); + const lua_Number k = (ik == 0) ? q : r + r*q; /* new key */ + TValue kv; + setfltvalue(&kv, k); + /* result is not an integral value, unless value is too large */ + lua_assert(!luaV_flttointeger(k, &ik, F2Ieq) || + l_mathop(fabs)(r) >= l_mathop(1e6)); + return addk(fs, &kv, &o); + } } -static int boolK (FuncState *fs, int b) { +/* +** Add a false to list of constants and return its index. +*/ +static int boolF (FuncState *fs) { TValue o; - setbvalue(&o, b); - return addk(fs, &o, &o); + setbfvalue(&o); + return addk(fs, &o, &o); /* use boolean itself as key */ } +/* +** Add a true to list of constants and return its index. +*/ +static int boolT (FuncState *fs) { + TValue o; + setbtvalue(&o); + return addk(fs, &o, &o); /* use boolean itself as key */ +} + + +/* +** Add nil to list of constants and return its index. +*/ static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); @@ -380,54 +651,165 @@ static int nilK (FuncState *fs) { } -void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { - if (e->k == VCALL) { /* expression is an open function call? */ - SETARG_C(getcode(fs, e), nresults+1); +/* +** Check whether 'i' can be stored in an 'sC' operand. Equivalent to +** (0 <= int2sC(i) && int2sC(i) <= MAXARG_C) but without risk of +** overflows in the hidden addition inside 'int2sC'. +*/ +static int fitsC (lua_Integer i) { + return (l_castS2U(i) + OFFSET_sC <= cast_uint(MAXARG_C)); +} + + +/* +** Check whether 'i' can be stored in an 'sBx' operand. +*/ +static int fitsBx (lua_Integer i) { + return (-OFFSET_sBx <= i && i <= MAXARG_Bx - OFFSET_sBx); +} + + +void luaK_int (FuncState *fs, int reg, lua_Integer i) { + if (fitsBx(i)) + codeAsBx(fs, OP_LOADI, reg, cast_int(i)); + else + luaK_codek(fs, reg, luaK_intK(fs, i)); +} + + +static void luaK_float (FuncState *fs, int reg, lua_Number f) { + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ieq) && fitsBx(fi)) + codeAsBx(fs, OP_LOADF, reg, cast_int(fi)); + else + luaK_codek(fs, reg, luaK_numberK(fs, f)); +} + + +/* +** Convert a constant in 'v' into an expression description 'e' +*/ +static void const2exp (TValue *v, expdesc *e) { + switch (ttypetag(v)) { + case LUA_VNUMINT: + e->k = VKINT; e->u.ival = ivalue(v); + break; + case LUA_VNUMFLT: + e->k = VKFLT; e->u.nval = fltvalue(v); + break; + case LUA_VFALSE: + e->k = VFALSE; + break; + case LUA_VTRUE: + e->k = VTRUE; + break; + case LUA_VNIL: + e->k = VNIL; + break; + case LUA_VSHRSTR: case LUA_VLNGSTR: + e->k = VKSTR; e->u.strval = tsvalue(v); + break; + default: lua_assert(0); } - else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), nresults+1); - SETARG_A(getcode(fs, e), fs->freereg); +} + + +/* +** Fix an expression to return the number of results 'nresults'. +** 'e' must be a multi-ret expression (function call or vararg). +*/ +void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { + Instruction *pc = &getinstruction(fs, e); + if (e->k == VCALL) /* expression is an open function call? */ + SETARG_C(*pc, nresults + 1); + else { + lua_assert(e->k == VVARARG); + SETARG_C(*pc, nresults + 1); + SETARG_A(*pc, fs->freereg); luaK_reserveregs(fs, 1); } } +/* +** Convert a VKSTR to a VK +*/ +static void str2K (FuncState *fs, expdesc *e) { + lua_assert(e->k == VKSTR); + e->u.info = stringK(fs, e->u.strval); + e->k = VK; +} + + +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOC (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ void luaK_setoneret (FuncState *fs, expdesc *e) { if (e->k == VCALL) { /* expression is an open function call? */ - e->k = VNONRELOC; - e->u.info = GETARG_A(getcode(fs, e)); + /* already returns 1 value */ + lua_assert(GETARG_C(getinstruction(fs, e)) == 2); + e->k = VNONRELOC; /* result has fixed position */ + e->u.info = GETARG_A(getinstruction(fs, e)); } else if (e->k == VVARARG) { - SETARG_B(getcode(fs, e), 2); - e->k = VRELOCABLE; /* can relocate its simple result */ + SETARG_C(getinstruction(fs, e), 2); + e->k = VRELOC; /* can relocate its simple result */ } } +/* +** Ensure that expression 'e' is not a variable (nor a ). +** (Expression still may have jump lists.) +*/ void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { - case VLOCAL: { - e->k = VNONRELOC; + case VCONST: { + const2exp(const2val(fs, e), e); break; } - case VUPVAL: { + case VLOCAL: { /* already in a register */ + int temp = e->u.var.ridx; + e->u.info = temp; /* (can't do a direct assignment; values overlap) */ + e->k = VNONRELOC; /* becomes a non-relocatable value */ + break; + } + case VUPVAL: { /* move value to some (pending) register */ e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); - e->k = VRELOCABLE; + e->k = VRELOC; + break; + } + case VINDEXUP: { + e->u.info = luaK_codeABC(fs, OP_GETTABUP, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXI: { + freereg(fs, e->u.ind.t); + e->u.info = luaK_codeABC(fs, OP_GETI, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXSTR: { + freereg(fs, e->u.ind.t); + e->u.info = luaK_codeABC(fs, OP_GETFIELD, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; break; } case VINDEXED: { - OpCode op = OP_GETTABUP; /* assume 't' is in an upvalue */ - freereg(fs, e->u.ind.idx); - if (e->u.ind.vt == VLOCAL) { /* 't' is in a register? */ - freereg(fs, e->u.ind.t); - op = OP_GETTABLE; - } - e->u.info = luaK_codeABC(fs, op, 0, e->u.ind.t, e->u.ind.idx); - e->k = VRELOCABLE; + freeregs(fs, e->u.ind.t, e->u.ind.idx); + e->u.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; break; } - case VVARARG: - case VCALL: { + case VVARARG: case VCALL: { luaK_setoneret(fs, e); break; } @@ -436,12 +818,11 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { } -static int code_label (FuncState *fs, int A, int b, int jump) { - luaK_getlabel(fs); /* those instructions may be jump targets */ - return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); -} - - +/* +** Ensure expression value is in register 'reg', making 'e' a +** non-relocatable expression. +** (Expression still may have jump lists.) +*/ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { @@ -449,25 +830,32 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_nil(fs, reg, 1); break; } - case VFALSE: case VTRUE: { - luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0); + case VFALSE: { + luaK_codeABC(fs, OP_LOADFALSE, reg, 0, 0); + break; + } + case VTRUE: { + luaK_codeABC(fs, OP_LOADTRUE, reg, 0, 0); break; } + case VKSTR: { + str2K(fs, e); + } /* FALLTHROUGH */ case VK: { luaK_codek(fs, reg, e->u.info); break; } case VKFLT: { - luaK_codek(fs, reg, luaK_numberK(fs, e->u.nval)); + luaK_float(fs, reg, e->u.nval); break; } case VKINT: { - luaK_codek(fs, reg, luaK_intK(fs, e->u.ival)); + luaK_int(fs, reg, e->u.ival); break; } - case VRELOCABLE: { - Instruction *pc = &getcode(fs, e); - SETARG_A(*pc, reg); + case VRELOC: { + Instruction *pc = &getinstruction(fs, e); + SETARG_A(*pc, reg); /* instruction will put result in 'reg' */ break; } case VNONRELOC: { @@ -476,26 +864,57 @@ static void discharge2reg (FuncState *fs, expdesc *e, int reg) { break; } default: { - lua_assert(e->k == VVOID || e->k == VJMP); + lua_assert(e->k == VJMP); return; /* nothing to do... */ } } - e->u.info = reg; - e->k = VNONRELOC; + e->u.info = reg; + e->k = VNONRELOC; +} + + +/* +** Ensure expression value is in a register, making 'e' a +** non-relocatable expression. +** (Expression still may have jump lists.) +*/ +static void discharge2anyreg (FuncState *fs, expdesc *e) { + if (e->k != VNONRELOC) { /* no fixed register yet? */ + luaK_reserveregs(fs, 1); /* get a register */ + discharge2reg(fs, e, fs->freereg-1); /* put value there */ + } } -static void discharge2anyreg (FuncState *fs, expdesc *e) { - if (e->k != VNONRELOC) { - luaK_reserveregs(fs, 1); - discharge2reg(fs, e, fs->freereg-1); +static int code_loadbool (FuncState *fs, int A, OpCode op) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, op, A, 0, 0); +} + + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; } + return 0; /* not found */ } +/* +** Ensures final expression result (which includes results from its +** jump lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ static void exp2reg (FuncState *fs, expdesc *e, int reg) { discharge2reg(fs, e, reg); - if (e->k == VJMP) + if (e->k == VJMP) /* expression itself is a test? */ luaK_concat(fs, &e->t, e->u.info); /* put this jump in 't' list */ if (hasjumps(e)) { int final; /* position after whole expression */ @@ -503,8 +922,9 @@ static void exp2reg (FuncState *fs, expdesc *e, int reg) { int p_t = NO_JUMP; /* position of an eventual LOAD true */ if (need_value(fs, e->t) || need_value(fs, e->f)) { int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); - p_f = code_label(fs, reg, 0, 1); - p_t = code_label(fs, reg, 1, 0); + p_f = code_loadbool(fs, reg, OP_LFALSESKIP); /* skip next inst. */ + p_t = code_loadbool(fs, reg, OP_LOADTRUE); + /* jump around these booleans if 'e' is not a test */ luaK_patchtohere(fs, fj); } final = luaK_getlabel(fs); @@ -517,6 +937,9 @@ static void exp2reg (FuncState *fs, expdesc *e, int reg) { } +/* +** Ensures final expression result is in next available register. +*/ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); freeexp(fs, e); @@ -525,26 +948,42 @@ void luaK_exp2nextreg (FuncState *fs, expdesc *e) { } +/* +** Ensures final expression result is in some (any) register +** and return that register. +*/ int luaK_exp2anyreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); - if (e->k == VNONRELOC) { - if (!hasjumps(e)) return e->u.info; /* exp is already in a register */ - if (e->u.info >= fs->nactvar) { /* reg. is not a local? */ - exp2reg(fs, e, e->u.info); /* put value on it */ + if (e->k == VNONRELOC) { /* expression already has a register? */ + if (!hasjumps(e)) /* no jumps? */ + return e->u.info; /* result is already in a register */ + if (e->u.info >= luaY_nvarstack(fs)) { /* reg. is not a local? */ + exp2reg(fs, e, e->u.info); /* put final result in it */ return e->u.info; } + /* else expression has jumps and cannot change its register + to hold the jump values, because it is a local variable. + Go through to the default case. */ } - luaK_exp2nextreg(fs, e); /* default */ + luaK_exp2nextreg(fs, e); /* default: use next available register */ return e->u.info; } +/* +** Ensures final expression result is either in a register +** or in an upvalue. +*/ void luaK_exp2anyregup (FuncState *fs, expdesc *e) { if (e->k != VUPVAL || hasjumps(e)) luaK_exp2anyreg(fs, e); } +/* +** Ensures final expression result is either in a register +** or it is a constant. +*/ void luaK_exp2val (FuncState *fs, expdesc *e) { if (hasjumps(e)) luaK_exp2anyreg(fs, e); @@ -553,47 +992,65 @@ void luaK_exp2val (FuncState *fs, expdesc *e) { } -int luaK_exp2RK (FuncState *fs, expdesc *e) { - luaK_exp2val(fs, e); - switch (e->k) { - case VTRUE: - case VFALSE: - case VNIL: { - if (fs->nk <= MAXINDEXRK) { /* constant fits in RK operand? */ - e->u.info = (e->k == VNIL) ? nilK(fs) : boolK(fs, (e->k == VTRUE)); - e->k = VK; - return RKASK(e->u.info); - } - else break; - } - case VKINT: { - e->u.info = luaK_intK(fs, e->u.ival); - e->k = VK; - goto vk; - } - case VKFLT: { - e->u.info = luaK_numberK(fs, e->u.nval); - e->k = VK; +/* +** Try to make 'e' a K expression with an index in the range of R/K +** indices. Return true iff succeeded. +*/ +static int luaK_exp2K (FuncState *fs, expdesc *e) { + if (!hasjumps(e)) { + int info; + switch (e->k) { /* move constants to 'k' */ + case VTRUE: info = boolT(fs); break; + case VFALSE: info = boolF(fs); break; + case VNIL: info = nilK(fs); break; + case VKINT: info = luaK_intK(fs, e->u.ival); break; + case VKFLT: info = luaK_numberK(fs, e->u.nval); break; + case VKSTR: info = stringK(fs, e->u.strval); break; + case VK: info = e->u.info; break; + default: return 0; /* not a constant */ } - /* FALLTHROUGH */ - case VK: { - vk: - if (e->u.info <= MAXINDEXRK) /* constant fits in 'argC'? */ - return RKASK(e->u.info); - else break; + if (info <= MAXINDEXRK) { /* does constant fit in 'argC'? */ + e->k = VK; /* make expression a 'K' expression */ + e->u.info = info; + return 1; } - default: break; } - /* not a constant in the right range: put it in a register */ - return luaK_exp2anyreg(fs, e); + /* else, expression doesn't fit; leave it unchanged */ + return 0; +} + + +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns 1 iff expression is K. +*/ +static int exp2RK (FuncState *fs, expdesc *e) { + if (luaK_exp2K(fs, e)) + return 1; + else { /* not a constant in the right range: put it in a register */ + luaK_exp2anyreg(fs, e); + return 0; + } +} + + +static void codeABRK (FuncState *fs, OpCode o, int a, int b, + expdesc *ec) { + int k = exp2RK(fs, ec); + luaK_codeABCk(fs, o, a, b, ec->u.info, k); } +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { switch (var->k) { case VLOCAL: { freeexp(fs, ex); - exp2reg(fs, ex, var->u.info); + exp2reg(fs, ex, var->u.var.ridx); /* compute 'ex' into proper place */ return; } case VUPVAL: { @@ -601,87 +1058,112 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0); break; } - case VINDEXED: { - OpCode op = (var->u.ind.vt == VLOCAL) ? OP_SETTABLE : OP_SETTABUP; - int e = luaK_exp2RK(fs, ex); - luaK_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); + case VINDEXUP: { + codeABRK(fs, OP_SETTABUP, var->u.ind.t, var->u.ind.idx, ex); break; } - default: { - lua_assert(0); /* invalid var kind to store */ + case VINDEXI: { + codeABRK(fs, OP_SETI, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXSTR: { + codeABRK(fs, OP_SETFIELD, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXED: { + codeABRK(fs, OP_SETTABLE, var->u.ind.t, var->u.ind.idx, ex); break; } + default: lua_assert(0); /* invalid var kind to store */ } freeexp(fs, ex); } +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { int ereg; luaK_exp2anyreg(fs, e); ereg = e->u.info; /* register where 'e' was placed */ freeexp(fs, e); e->u.info = fs->freereg; /* base register for op_self */ - e->k = VNONRELOC; + e->k = VNONRELOC; /* self expression has a fixed register */ luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ - luaK_codeABC(fs, OP_SELF, e->u.info, ereg, luaK_exp2RK(fs, key)); + codeABRK(fs, OP_SELF, e->u.info, ereg, key); freeexp(fs, key); } -static void invertjump (FuncState *fs, expdesc *e) { +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +static void negatecondition (FuncState *fs, expdesc *e) { Instruction *pc = getjumpcontrol(fs, e->u.info); lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && GET_OPCODE(*pc) != OP_TEST); - SETARG_A(*pc, !(GETARG_A(*pc))); + SETARG_k(*pc, (GETARG_k(*pc) ^ 1)); } +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ static int jumponcond (FuncState *fs, expdesc *e, int cond) { - if (e->k == VRELOCABLE) { - Instruction ie = getcode(fs, e); + if (e->k == VRELOC) { + Instruction ie = getinstruction(fs, e); if (GET_OPCODE(ie) == OP_NOT) { - fs->pc--; /* remove previous OP_NOT */ - return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); + removelastinstruction(fs); /* remove previous OP_NOT */ + return condjump(fs, OP_TEST, GETARG_B(ie), 0, 0, !cond); } /* else go through */ } discharge2anyreg(fs, e); freeexp(fs, e); - return condjump(fs, OP_TESTSET, NO_REG, e->u.info, cond); + return condjump(fs, OP_TESTSET, NO_REG, e->u.info, 0, cond); } +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ void luaK_goiftrue (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { - case VJMP: { - invertjump(fs, e); - pc = e->u.info; + case VJMP: { /* condition? */ + negatecondition(fs, e); /* jump when it is false */ + pc = e->u.info; /* save jump position */ break; } - case VK: case VKFLT: case VKINT: case VTRUE: { + case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: { pc = NO_JUMP; /* always true; do nothing */ break; } default: { - pc = jumponcond(fs, e, 0); + pc = jumponcond(fs, e, 0); /* jump when false */ break; } } - luaK_concat(fs, &e->f, pc); /* insert last jump in 'f' list */ - luaK_patchtohere(fs, e->t); + luaK_concat(fs, &e->f, pc); /* insert new jump in false list */ + luaK_patchtohere(fs, e->t); /* true list jumps to here (to go through) */ e->t = NO_JUMP; } +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ void luaK_goiffalse (FuncState *fs, expdesc *e) { - int pc; /* pc of last jump */ + int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { case VJMP: { - pc = e->u.info; + pc = e->u.info; /* already jump if true */ break; } case VNIL: case VFALSE: { @@ -689,70 +1171,156 @@ void luaK_goiffalse (FuncState *fs, expdesc *e) { break; } default: { - pc = jumponcond(fs, e, 1); + pc = jumponcond(fs, e, 1); /* jump if true */ break; } } - luaK_concat(fs, &e->t, pc); /* insert last jump in 't' list */ - luaK_patchtohere(fs, e->f); + luaK_concat(fs, &e->t, pc); /* insert new jump in 't' list */ + luaK_patchtohere(fs, e->f); /* false list jumps to here (to go through) */ e->f = NO_JUMP; } +/* +** Code 'not e', doing constant folding. +*/ static void codenot (FuncState *fs, expdesc *e) { - luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { - e->k = VTRUE; + e->k = VTRUE; /* true == not nil == not false */ break; } - case VK: case VKFLT: case VKINT: case VTRUE: { - e->k = VFALSE; + case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: { + e->k = VFALSE; /* false == not "x" == not 0.5 == not 1 == not true */ break; } case VJMP: { - invertjump(fs, e); + negatecondition(fs, e); break; } - case VRELOCABLE: + case VRELOC: case VNONRELOC: { discharge2anyreg(fs, e); freeexp(fs, e); e->u.info = luaK_codeABC(fs, OP_NOT, 0, e->u.info, 0); - e->k = VRELOCABLE; - break; - } - default: { - lua_assert(0); /* cannot happen */ + e->k = VRELOC; break; } + default: lua_assert(0); /* cannot happen */ } /* interchange true and false lists */ { int temp = e->f; e->f = e->t; e->t = temp; } - removevalues(fs, e->f); + removevalues(fs, e->f); /* values are useless when negated */ removevalues(fs, e->t); } +/* +** Check whether expression 'e' is a short literal string +*/ +static int isKstr (FuncState *fs, expdesc *e) { + return (e->k == VK && !hasjumps(e) && e->u.info <= MAXARG_B && + ttisshrstring(&fs->f->k[e->u.info])); +} + +/* +** Check whether expression 'e' is a literal integer. +*/ +static int isKint (expdesc *e) { + return (e->k == VKINT && !hasjumps(e)); +} + + +/* +** Check whether expression 'e' is a literal integer in +** proper range to fit in register C +*/ +static int isCint (expdesc *e) { + return isKint(e) && (l_castS2U(e->u.ival) <= l_castS2U(MAXARG_C)); +} + + +/* +** Check whether expression 'e' is a literal integer in +** proper range to fit in register sC +*/ +static int isSCint (expdesc *e) { + return isKint(e) && fitsC(e->u.ival); +} + + +/* +** Check whether expression 'e' is a literal integer or float in +** proper range to fit in a register (sB or sC). +*/ +static int isSCnumber (expdesc *e, int *pi, int *isfloat) { + lua_Integer i; + if (e->k == VKINT) + i = e->u.ival; + else if (e->k == VKFLT && luaV_flttointeger(e->u.nval, &i, F2Ieq)) + *isfloat = 1; + else + return 0; /* not a number */ + if (!hasjumps(e) && fitsC(i)) { + *pi = int2sC(cast_int(i)); + return 1; + } + else + return 0; +} + + +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. Upvalues can only be indexed by literal strings. +** Keys can be literal strings in the constant table or arbitrary +** values in registers. +*/ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { - lua_assert(!hasjumps(t)); - t->u.ind.t = t->u.info; - t->u.ind.idx = luaK_exp2RK(fs, k); - t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL - : check_exp(vkisinreg(t->k), VLOCAL); - t->k = VINDEXED; + if (k->k == VKSTR) + str2K(fs, k); + lua_assert(!hasjumps(t) && + (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL)); + if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */ + luaK_exp2anyreg(fs, t); /* put it in a register */ + if (t->k == VUPVAL) { + int temp = t->u.info; /* upvalue index */ + lua_assert(isKstr(fs, k)); + t->u.ind.t = temp; /* (can't do a direct assignment; values overlap) */ + t->u.ind.idx = k->u.info; /* literal short string */ + t->k = VINDEXUP; + } + else { + /* register index of the table */ + t->u.ind.t = (t->k == VLOCAL) ? t->u.var.ridx: t->u.info; + if (isKstr(fs, k)) { + t->u.ind.idx = k->u.info; /* literal short string */ + t->k = VINDEXSTR; + } + else if (isCint(k)) { + t->u.ind.idx = cast_int(k->u.ival); /* int. constant in proper range */ + t->k = VINDEXI; + } + else { + t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */ + t->k = VINDEXED; + } + } } /* -** return false if folding can raise an error +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. */ static int validop (int op, TValue *v1, TValue *v2) { switch (op) { case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: { /* conversion errors */ lua_Integer i; - return (tointeger(v1, &i) && tointeger(v2, &i)); + return (luaV_tointegerns(v1, &i, LUA_FLOORN2I) && + luaV_tointegerns(v2, &i, LUA_FLOORN2I)); } case LUA_OPDIV: case LUA_OPIDIV: case LUA_OPMOD: /* division by 0 */ return (nvalue(v2) != 0); @@ -762,18 +1330,20 @@ static int validop (int op, TValue *v1, TValue *v2) { /* -** Try to "constant-fold" an operation; return 1 iff successful +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) */ -static int constfolding (FuncState *fs, int op, expdesc *e1, expdesc *e2) { +static int constfolding (FuncState *fs, int op, expdesc *e1, + const expdesc *e2) { TValue v1, v2, res; if (!tonumeral(e1, &v1) || !tonumeral(e2, &v2) || !validop(op, &v1, &v2)) return 0; /* non-numeric operands or not safe to fold */ - luaO_arith(fs->ls->L, op, &v1, &v2, &res); /* does operation */ + luaO_rawarith(fs->ls->L, op, &v1, &v2, &res); /* does operation */ if (ttisinteger(&res)) { e1->k = VKINT; e1->u.ival = ivalue(&res); } - else { /* folds neither NaN nor 0.0 (to avoid collapsing with -0.0) */ + else { /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ lua_Number n = fltvalue(&res); if (luai_numisnan(n) || n == 0) return 0; @@ -785,85 +1355,297 @@ static int constfolding (FuncState *fs, int op, expdesc *e1, expdesc *e2) { /* -** Code for binary and unary expressions that "produce values" -** (arithmetic operations, bitwise operations, concat, length). First -** try to do constant folding (only for numeric [arithmetic and -** bitwise] operations, which is what 'lua_arith' accepts). +** Convert a BinOpr to an OpCode (ORDER OPR - ORDER OP) +*/ +l_sinline OpCode binopr2op (BinOpr opr, BinOpr baser, OpCode base) { + lua_assert(baser <= opr && + ((baser == OPR_ADD && opr <= OPR_SHR) || + (baser == OPR_LT && opr <= OPR_LE))); + return cast(OpCode, (cast_int(opr) - cast_int(baser)) + cast_int(base)); +} + + +/* +** Convert a UnOpr to an OpCode (ORDER OPR - ORDER OP) +*/ +l_sinline OpCode unopr2op (UnOpr opr) { + return cast(OpCode, (cast_int(opr) - cast_int(OPR_MINUS)) + + cast_int(OP_UNM)); +} + + +/* +** Convert a BinOpr to a tag method (ORDER OPR - ORDER TM) +*/ +l_sinline TMS binopr2TM (BinOpr opr) { + lua_assert(OPR_ADD <= opr && opr <= OPR_SHR); + return cast(TMS, (cast_int(opr) - cast_int(OPR_ADD)) + cast_int(TM_ADD)); +} + + +/* +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. +*/ +static void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) { + int r = luaK_exp2anyreg(fs, e); /* opcodes operate only on registers */ + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, op, 0, r, 0); /* generate opcode */ + e->k = VRELOC; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). ** Expression to produce final result will be encoded in 'e1'. */ -static void codeexpval (FuncState *fs, OpCode op, - expdesc *e1, expdesc *e2, int line) { - lua_assert(op >= OP_ADD); - if (op <= OP_BNOT && constfolding(fs, (op - OP_ADD) + LUA_OPADD, e1, e2)) - return; /* result has been folded */ +static void finishbinexpval (FuncState *fs, expdesc *e1, expdesc *e2, + OpCode op, int v2, int flip, int line, + OpCode mmop, TMS event) { + int v1 = luaK_exp2anyreg(fs, e1); + int pc = luaK_codeABCk(fs, op, 0, v1, v2, 0); + freeexps(fs, e1, e2); + e1->u.info = pc; + e1->k = VRELOC; /* all those operations are relocatable */ + luaK_fixline(fs, line); + luaK_codeABCk(fs, mmop, v1, v2, event, flip); /* to call metamethod */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" over +** two registers. +*/ +static void codebinexpval (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int line) { + OpCode op = binopr2op(opr, OPR_ADD, OP_ADD); + int v2 = luaK_exp2anyreg(fs, e2); /* make sure 'e2' is in a register */ + /* 'e1' must be already in a register or it is a constant */ + lua_assert((VNIL <= e1->k && e1->k <= VKSTR) || + e1->k == VNONRELOC || e1->k == VRELOC); + lua_assert(OP_ADD <= op && op <= OP_SHR); + finishbinexpval(fs, e1, e2, op, v2, 0, line, OP_MMBIN, binopr2TM(opr)); +} + + +/* +** Code binary operators with immediate operands. +*/ +static void codebini (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int flip, int line, + TMS event) { + int v2 = int2sC(cast_int(e2->u.ival)); /* immediate operand */ + lua_assert(e2->k == VKINT); + finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINI, event); +} + + +/* +** Code binary operators with K operand. +*/ +static void codebinK (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int flip, int line) { + TMS event = binopr2TM(opr); + int v2 = e2->u.info; /* K index */ + OpCode op = binopr2op(opr, OPR_ADD, OP_ADDK); + finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINK, event); +} + + +/* Try to code a binary operator negating its second operand. +** For the metamethod, 2nd operand must keep its original value. +*/ +static int finishbinexpneg (FuncState *fs, expdesc *e1, expdesc *e2, + OpCode op, int line, TMS event) { + if (!isKint(e2)) + return 0; /* not an integer constant */ else { - int o1, o2; - /* move operands to registers (if needed) */ - if (op == OP_UNM || op == OP_BNOT || op == OP_LEN) { /* unary op? */ - o2 = 0; /* no second expression */ - o1 = luaK_exp2anyreg(fs, e1); /* cannot operate on constants */ - } - else { /* regular case (binary operators) */ - o2 = luaK_exp2RK(fs, e2); /* both operands are "RK" */ - o1 = luaK_exp2RK(fs, e1); - } - if (o1 > o2) { /* free registers in proper order */ - freeexp(fs, e1); - freeexp(fs, e2); + lua_Integer i2 = e2->u.ival; + if (!(fitsC(i2) && fitsC(-i2))) + return 0; /* not in the proper range */ + else { /* operating a small integer constant */ + int v2 = cast_int(i2); + finishbinexpval(fs, e1, e2, op, int2sC(-v2), 0, line, OP_MMBINI, event); + /* correct metamethod argument */ + SETARG_B(fs->f->code[fs->pc - 1], int2sC(v2)); + return 1; /* successfully coded */ } - else { - freeexp(fs, e2); - freeexp(fs, e1); - } - e1->u.info = luaK_codeABC(fs, op, 0, o1, o2); /* generate opcode */ - e1->k = VRELOCABLE; /* all those operations are relocable */ - luaK_fixline(fs, line); } } -static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, - expdesc *e2) { - int o1 = luaK_exp2RK(fs, e1); - int o2 = luaK_exp2RK(fs, e2); - freeexp(fs, e2); - freeexp(fs, e1); - if (cond == 0 && op != OP_EQ) { - int temp; /* exchange args to replace by '<' or '<=' */ - temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ - cond = 1; +static void swapexps (expdesc *e1, expdesc *e2) { + expdesc temp = *e1; *e1 = *e2; *e2 = temp; /* swap 'e1' and 'e2' */ +} + + +/* +** Code binary operators with no constant operand. +*/ +static void codebinNoK (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int flip, int line) { + if (flip) + swapexps(e1, e2); /* back to original order */ + codebinexpval(fs, opr, e1, e2, line); /* use standard operators */ +} + + +/* +** Code arithmetic operators ('+', '-', ...). If second operand is a +** constant in the proper range, use variant opcodes with K operands. +*/ +static void codearith (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int flip, int line) { + if (tonumeral(e2, NULL) && luaK_exp2K(fs, e2)) /* K operand? */ + codebinK(fs, opr, e1, e2, flip, line); + else /* 'e2' is neither an immediate nor a K operand */ + codebinNoK(fs, opr, e1, e2, flip, line); +} + + +/* +** Code commutative operators ('+', '*'). If first operand is a +** numeric constant, change order of operands to try to use an +** immediate or K operator. +*/ +static void codecommutative (FuncState *fs, BinOpr op, + expdesc *e1, expdesc *e2, int line) { + int flip = 0; + if (tonumeral(e1, NULL)) { /* is first operand a numeric constant? */ + swapexps(e1, e2); /* change order */ + flip = 1; + } + if (op == OPR_ADD && isSCint(e2)) /* immediate operand? */ + codebini(fs, OP_ADDI, e1, e2, flip, line, TM_ADD); + else + codearith(fs, op, e1, e2, flip, line); +} + + +/* +** Code bitwise operations; they are all commutative, so the function +** tries to put an integer constant as the 2nd operand (a K operand). +*/ +static void codebitwise (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int line) { + int flip = 0; + if (e1->k == VKINT) { + swapexps(e1, e2); /* 'e2' will be the constant operand */ + flip = 1; + } + if (e2->k == VKINT && luaK_exp2K(fs, e2)) /* K operand? */ + codebinK(fs, opr, e1, e2, flip, line); + else /* no constants */ + codebinNoK(fs, opr, e1, e2, flip, line); +} + + +/* +** Emit code for order comparisons. When using an immediate operand, +** 'isfloat' tells whether the original value was a float. +*/ +static void codeorder (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int r1, r2; + int im; + int isfloat = 0; + OpCode op; + if (isSCnumber(e2, &im, &isfloat)) { + /* use immediate operand */ + r1 = luaK_exp2anyreg(fs, e1); + r2 = im; + op = binopr2op(opr, OPR_LT, OP_LTI); + } + else if (isSCnumber(e1, &im, &isfloat)) { + /* transform (A < B) to (B > A) and (A <= B) to (B >= A) */ + r1 = luaK_exp2anyreg(fs, e2); + r2 = im; + op = binopr2op(opr, OPR_LT, OP_GTI); } - e1->u.info = condjump(fs, op, cond, o1, o2); + else { /* regular case, compare two registers */ + r1 = luaK_exp2anyreg(fs, e1); + r2 = luaK_exp2anyreg(fs, e2); + op = binopr2op(opr, OPR_LT, OP_LT); + } + freeexps(fs, e1, e2); + e1->u.info = condjump(fs, op, r1, r2, isfloat, 1); e1->k = VJMP; } -void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e, int line) { - expdesc e2; - e2.t = e2.f = NO_JUMP; e2.k = VKINT; e2.u.ival = 0; - switch (op) { - case OPR_MINUS: case OPR_BNOT: case OPR_LEN: { - codeexpval(fs, cast(OpCode, (op - OPR_MINUS) + OP_UNM), e, &e2, line); +/* +** Emit code for equality comparisons ('==', '~='). +** 'e1' was already put as RK by 'luaK_infix'. +*/ +static void codeeq (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int r1, r2; + int im; + int isfloat = 0; /* not needed here, but kept for symmetry */ + OpCode op; + if (e1->k != VNONRELOC) { + lua_assert(e1->k == VK || e1->k == VKINT || e1->k == VKFLT); + swapexps(e1, e2); + } + r1 = luaK_exp2anyreg(fs, e1); /* 1st expression must be in register */ + if (isSCnumber(e2, &im, &isfloat)) { + op = OP_EQI; + r2 = im; /* immediate operand */ + } + else if (exp2RK(fs, e2)) { /* 2nd expression is constant? */ + op = OP_EQK; + r2 = e2->u.info; /* constant index */ + } + else { + op = OP_EQ; /* will compare two registers */ + r2 = luaK_exp2anyreg(fs, e2); + } + freeexps(fs, e1, e2); + e1->u.info = condjump(fs, op, r1, r2, isfloat, (opr == OPR_EQ)); + e1->k = VJMP; +} + + +/* +** Apply prefix operation 'op' to expression 'e'. +*/ +void luaK_prefix (FuncState *fs, UnOpr opr, expdesc *e, int line) { + static const expdesc ef = {VKINT, {0}, NO_JUMP, NO_JUMP}; + luaK_dischargevars(fs, e); + switch (opr) { + case OPR_MINUS: case OPR_BNOT: /* use 'ef' as fake 2nd operand */ + if (constfolding(fs, opr + LUA_OPUNM, e, &ef)) + break; + /* else */ /* FALLTHROUGH */ + case OPR_LEN: + codeunexpval(fs, unopr2op(opr), e, line); break; - } case OPR_NOT: codenot(fs, e); break; default: lua_assert(0); } } +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { + luaK_dischargevars(fs, v); switch (op) { case OPR_AND: { - luaK_goiftrue(fs, v); + luaK_goiftrue(fs, v); /* go ahead only if 'v' is true */ break; } case OPR_OR: { - luaK_goiffalse(fs, v); + luaK_goiffalse(fs, v); /* go ahead only if 'v' is false */ break; } case OPR_CONCAT: { - luaK_exp2nextreg(fs, v); /* operand must be on the 'stack' */ + luaK_exp2nextreg(fs, v); /* operand must be on the stack */ break; } case OPR_ADD: case OPR_SUB: @@ -871,61 +1653,125 @@ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { case OPR_MOD: case OPR_POW: case OPR_BAND: case OPR_BOR: case OPR_BXOR: case OPR_SHL: case OPR_SHR: { - if (!tonumeral(v, NULL)) luaK_exp2RK(fs, v); + if (!tonumeral(v, NULL)) + luaK_exp2anyreg(fs, v); + /* else keep numeral, which may be folded or used as an immediate + operand */ break; } - default: { - luaK_exp2RK(fs, v); + case OPR_EQ: case OPR_NE: { + if (!tonumeral(v, NULL)) + exp2RK(fs, v); + /* else keep numeral, which may be an immediate operand */ break; } + case OPR_LT: case OPR_LE: + case OPR_GT: case OPR_GE: { + int dummy, dummy2; + if (!isSCnumber(v, &dummy, &dummy2)) + luaK_exp2anyreg(fs, v); + /* else keep numeral, which may be an immediate operand */ + break; + } + default: lua_assert(0); + } +} + +/* +** Create code for '(e1 .. e2)'. +** For '(e1 .. e2.1 .. e2.2)' (which is '(e1 .. (e2.1 .. e2.2))', +** because concatenation is right associative), merge both CONCATs. +*/ +static void codeconcat (FuncState *fs, expdesc *e1, expdesc *e2, int line) { + Instruction *ie2 = previousinstruction(fs); + if (GET_OPCODE(*ie2) == OP_CONCAT) { /* is 'e2' a concatenation? */ + int n = GETARG_B(*ie2); /* # of elements concatenated in 'e2' */ + lua_assert(e1->u.info + 1 == GETARG_A(*ie2)); + freeexp(fs, e2); + SETARG_A(*ie2, e1->u.info); /* correct first element ('e1') */ + SETARG_B(*ie2, n + 1); /* will concatenate one more element */ + } + else { /* 'e2' is not a concatenation */ + luaK_codeABC(fs, OP_CONCAT, e1->u.info, 2, 0); /* new concat opcode */ + freeexp(fs, e2); + luaK_fixline(fs, line); } } -void luaK_posfix (FuncState *fs, BinOpr op, +/* +** Finalize code for binary operation, after reading 2nd operand. +*/ +void luaK_posfix (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2, int line) { - switch (op) { + luaK_dischargevars(fs, e2); + if (foldbinop(opr) && constfolding(fs, opr + LUA_OPADD, e1, e2)) + return; /* done by folding */ + switch (opr) { case OPR_AND: { - lua_assert(e1->t == NO_JUMP); /* list must be closed */ - luaK_dischargevars(fs, e2); + lua_assert(e1->t == NO_JUMP); /* list closed by 'luaK_infix' */ luaK_concat(fs, &e2->f, e1->f); *e1 = *e2; break; } case OPR_OR: { - lua_assert(e1->f == NO_JUMP); /* list must be closed */ - luaK_dischargevars(fs, e2); + lua_assert(e1->f == NO_JUMP); /* list closed by 'luaK_infix' */ luaK_concat(fs, &e2->t, e1->t); *e1 = *e2; break; } - case OPR_CONCAT: { - luaK_exp2val(fs, e2); - if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { - lua_assert(e1->u.info == GETARG_B(getcode(fs, e2))-1); - freeexp(fs, e1); - SETARG_B(getcode(fs, e2), e1->u.info); - e1->k = VRELOCABLE; e1->u.info = e2->u.info; + case OPR_CONCAT: { /* e1 .. e2 */ + luaK_exp2nextreg(fs, e2); + codeconcat(fs, e1, e2, line); + break; + } + case OPR_ADD: case OPR_MUL: { + codecommutative(fs, opr, e1, e2, line); + break; + } + case OPR_SUB: { + if (finishbinexpneg(fs, e1, e2, OP_ADDI, line, TM_SUB)) + break; /* coded as (r1 + -I) */ + /* ELSE */ + } /* FALLTHROUGH */ + case OPR_DIV: case OPR_IDIV: case OPR_MOD: case OPR_POW: { + codearith(fs, opr, e1, e2, 0, line); + break; + } + case OPR_BAND: case OPR_BOR: case OPR_BXOR: { + codebitwise(fs, opr, e1, e2, line); + break; + } + case OPR_SHL: { + if (isSCint(e1)) { + swapexps(e1, e2); + codebini(fs, OP_SHLI, e1, e2, 1, line, TM_SHL); /* I << r2 */ } - else { - luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ - codeexpval(fs, OP_CONCAT, e1, e2, line); + else if (finishbinexpneg(fs, e1, e2, OP_SHRI, line, TM_SHL)) { + /* coded as (r1 >> -I) */; } + else /* regular case (two registers) */ + codebinexpval(fs, opr, e1, e2, line); break; } - case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: - case OPR_IDIV: case OPR_MOD: case OPR_POW: - case OPR_BAND: case OPR_BOR: case OPR_BXOR: - case OPR_SHL: case OPR_SHR: { - codeexpval(fs, cast(OpCode, (op - OPR_ADD) + OP_ADD), e1, e2, line); + case OPR_SHR: { + if (isSCint(e2)) + codebini(fs, OP_SHRI, e1, e2, 0, line, TM_SHR); /* r1 >> I */ + else /* regular case (two registers) */ + codebinexpval(fs, opr, e1, e2, line); break; } - case OPR_EQ: case OPR_LT: case OPR_LE: { - codecomp(fs, cast(OpCode, (op - OPR_EQ) + OP_EQ), 1, e1, e2); + case OPR_EQ: case OPR_NE: { + codeeq(fs, opr, e1, e2); break; } - case OPR_NE: case OPR_GT: case OPR_GE: { - codecomp(fs, cast(OpCode, (op - OPR_NE) + OP_EQ), 0, e1, e2); + case OPR_GT: case OPR_GE: { + /* '(a > b)' <=> '(b < a)'; '(a >= b)' <=> '(b <= a)' */ + swapexps(e1, e2); + opr = cast(BinOpr, (opr - OPR_GT) + OPR_LT); + } /* FALLTHROUGH */ + case OPR_LT: case OPR_LE: { + codeorder(fs, opr, e1, e2); break; } default: lua_assert(0); @@ -933,23 +1779,96 @@ void luaK_posfix (FuncState *fs, BinOpr op, } +/* +** Change line information associated with current position, by removing +** previous info and adding it again with new line. +*/ void luaK_fixline (FuncState *fs, int line) { - fs->f->lineinfo[fs->pc - 1] = line; + removelastlineinfo(fs); + savelineinfo(fs, fs->f, line); +} + + +void luaK_settablesize (FuncState *fs, int pc, int ra, int asize, int hsize) { + Instruction *inst = &fs->f->code[pc]; + int rb = (hsize != 0) ? luaO_ceillog2(hsize) + 1 : 0; /* hash size */ + int extra = asize / (MAXARG_C + 1); /* higher bits of array size */ + int rc = asize % (MAXARG_C + 1); /* lower bits of array size */ + int k = (extra > 0); /* true iff needs extra argument */ + *inst = CREATE_ABCk(OP_NEWTABLE, ra, rb, rc, k); + *(inst + 1) = CREATE_Ax(OP_EXTRAARG, extra); } +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { - int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; - int b = (tostore == LUA_MULTRET) ? 0 : tostore; - lua_assert(tostore != 0); - if (c <= MAXARG_C) - luaK_codeABC(fs, OP_SETLIST, base, b, c); - else if (c <= MAXARG_Ax) { - luaK_codeABC(fs, OP_SETLIST, base, b, 0); - codeextraarg(fs, c); + lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH); + if (tostore == LUA_MULTRET) + tostore = 0; + if (nelems <= MAXARG_C) + luaK_codeABC(fs, OP_SETLIST, base, tostore, nelems); + else { + int extra = nelems / (MAXARG_C + 1); + nelems %= (MAXARG_C + 1); + luaK_codeABCk(fs, OP_SETLIST, base, tostore, nelems, 1); + codeextraarg(fs, extra); } - else - luaX_syntaxerror(fs->ls, "constructor too long"); fs->freereg = base + 1; /* free registers with list values */ } + +/* +** return the final target of a jump (skipping jumps to jumps) +*/ +static int finaltarget (Instruction *code, int i) { + int count; + for (count = 0; count < 100; count++) { /* avoid infinite loops */ + Instruction pc = code[i]; + if (GET_OPCODE(pc) != OP_JMP) + break; + else + i += GETARG_sJ(pc) + 1; + } + return i; +} + + +/* +** Do a final pass over the code of a function, doing small peephole +** optimizations and adjustments. +*/ +void luaK_finish (FuncState *fs) { + int i; + Proto *p = fs->f; + for (i = 0; i < fs->pc; i++) { + Instruction *pc = &p->code[i]; + lua_assert(i == 0 || isOT(*(pc - 1)) == isIT(*pc)); + switch (GET_OPCODE(*pc)) { + case OP_RETURN0: case OP_RETURN1: { + if (!(fs->needclose || p->is_vararg)) + break; /* no extra work */ + /* else use OP_RETURN to do the extra work */ + SET_OPCODE(*pc, OP_RETURN); + } /* FALLTHROUGH */ + case OP_RETURN: case OP_TAILCALL: { + if (fs->needclose) + SETARG_k(*pc, 1); /* signal that it needs to close */ + if (p->is_vararg) + SETARG_C(*pc, p->numparams + 1); /* signal that it is vararg */ + break; + } + case OP_JMP: { + int target = finaltarget(p->code, i); + fixjump(fs, i, target); + break; + } + default: break; + } + } +} diff --git a/libs/lua/lcode.h b/libs/lua/lcode.h index 43ab86db..0b971fc4 100644 --- a/libs/lua/lcode.h +++ b/libs/lua/lcode.h @@ -1,5 +1,5 @@ /* -** $Id: lcode.h,v 1.63 2013/12/30 20:47:58 roberto Exp $ +** $Id: lcode.h $ ** Code generator for Lua ** See Copyright Notice in lua.h */ @@ -24,45 +24,56 @@ ** grep "ORDER OPR" if you change these enums (ORDER OP) */ typedef enum BinOpr { + /* arithmetic operators */ OPR_ADD, OPR_SUB, OPR_MUL, OPR_MOD, OPR_POW, - OPR_DIV, - OPR_IDIV, + OPR_DIV, OPR_IDIV, + /* bitwise operators */ OPR_BAND, OPR_BOR, OPR_BXOR, OPR_SHL, OPR_SHR, + /* string operator */ OPR_CONCAT, + /* comparison operators */ OPR_EQ, OPR_LT, OPR_LE, OPR_NE, OPR_GT, OPR_GE, + /* logical operators */ OPR_AND, OPR_OR, OPR_NOBINOPR } BinOpr; +/* true if operation is foldable (that is, it is arithmetic or bitwise) */ +#define foldbinop(op) ((op) <= OPR_SHR) + + +#define luaK_codeABC(fs,o,a,b,c) luaK_codeABCk(fs,o,a,b,c,0) + + typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; -#define getcode(fs,e) ((fs)->f->code[(e)->u.info]) +/* get (pointer to) instruction of given 'expdesc' */ +#define getinstruction(fs,e) ((fs)->f->code[(e)->u.info]) -#define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) #define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET) #define luaK_jumpto(fs,t) luaK_patchlist(fs, luaK_jump(fs), t) +LUAI_FUNC int luaK_code (FuncState *fs, Instruction i); LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); -LUAI_FUNC int luaK_codeABC (FuncState *fs, OpCode o, int A, int B, int C); -LUAI_FUNC int luaK_codek (FuncState *fs, int reg, int k); +LUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A, + int B, int C, int k); +LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v); LUAI_FUNC void luaK_fixline (FuncState *fs, int line); LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); -LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s); -LUAI_FUNC int luaK_intK (FuncState *fs, lua_Integer n); +LUAI_FUNC void luaK_int (FuncState *fs, int reg, lua_Integer n); LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); -LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); @@ -74,14 +85,17 @@ LUAI_FUNC int luaK_jump (FuncState *fs); LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret); LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target); LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list); -LUAI_FUNC void luaK_patchclose (FuncState *fs, int list, int level); LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2); LUAI_FUNC int luaK_getlabel (FuncState *fs); LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line); LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v); LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, expdesc *v2, int line); +LUAI_FUNC void luaK_settablesize (FuncState *fs, int pc, + int ra, int asize, int hsize); LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); +LUAI_FUNC void luaK_finish (FuncState *fs); +LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg); #endif diff --git a/libs/lua/lcorolib.c b/libs/lua/lcorolib.c index 0c0b7fa6..c64adf08 100644 --- a/libs/lua/lcorolib.c +++ b/libs/lua/lcorolib.c @@ -1,5 +1,5 @@ /* -** $Id: lcorolib.c,v 1.9 2014/11/02 19:19:04 roberto Exp $ +** $Id: lcorolib.c $ ** Coroutine Library ** See Copyright Notice in lua.h */ @@ -20,26 +20,25 @@ static lua_State *getco (lua_State *L) { lua_State *co = lua_tothread(L, 1); - luaL_argcheck(L, co, 1, "thread expected"); + luaL_argexpected(L, co, 1, "thread"); return co; } +/* +** Resumes a coroutine. Returns the number of results for non-error +** cases or -1 for errors. +*/ static int auxresume (lua_State *L, lua_State *co, int narg) { - int status; - if (!lua_checkstack(co, narg)) { + int status, nres; + if (l_unlikely(!lua_checkstack(co, narg))) { lua_pushliteral(L, "too many arguments to resume"); return -1; /* error flag */ } - if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) { - lua_pushliteral(L, "cannot resume dead coroutine"); - return -1; /* error flag */ - } lua_xmove(L, co, narg); - status = lua_resume(co, L, narg); - if (status == LUA_OK || status == LUA_YIELD) { - int nres = lua_gettop(co); - if (!lua_checkstack(L, nres + 1)) { + status = lua_resume(co, L, narg, &nres); + if (l_likely(status == LUA_OK || status == LUA_YIELD)) { + if (l_unlikely(!lua_checkstack(L, nres + 1))) { lua_pop(co, nres); /* remove results anyway */ lua_pushliteral(L, "too many results to resume"); return -1; /* error flag */ @@ -58,7 +57,7 @@ static int luaB_coresume (lua_State *L) { lua_State *co = getco(L); int r; r = auxresume(L, co, lua_gettop(L) - 1); - if (r < 0) { + if (l_unlikely(r < 0)) { lua_pushboolean(L, 0); lua_insert(L, -2); return 2; /* return false + error message */ @@ -74,9 +73,16 @@ static int luaB_coresume (lua_State *L) { static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); - if (r < 0) { - if (lua_isstring(L, -1)) { /* error object is a string? */ - luaL_where(L, 1); /* add extra info */ + if (l_unlikely(r < 0)) { /* error? */ + int stat = lua_status(co); + if (stat != LUA_OK && stat != LUA_YIELD) { /* error in the coroutine? */ + stat = lua_closethread(co, L); /* close its tbc variables */ + lua_assert(stat != LUA_OK); + lua_xmove(co, L, 1); /* move error message to the caller */ + } + if (stat != LUA_ERRMEM && /* not a memory error and ... */ + lua_type(L, -1) == LUA_TSTRING) { /* ... error object is a string? */ + luaL_where(L, 1); /* add extra info, if available */ lua_insert(L, -2); lua_concat(L, 2); } @@ -108,35 +114,48 @@ static int luaB_yield (lua_State *L) { } -static int luaB_costatus (lua_State *L) { - lua_State *co = getco(L); - if (L == co) lua_pushliteral(L, "running"); +#define COS_RUN 0 +#define COS_DEAD 1 +#define COS_YIELD 2 +#define COS_NORM 3 + + +static const char *const statname[] = + {"running", "dead", "suspended", "normal"}; + + +static int auxstatus (lua_State *L, lua_State *co) { + if (L == co) return COS_RUN; else { switch (lua_status(co)) { case LUA_YIELD: - lua_pushliteral(L, "suspended"); - break; + return COS_YIELD; case LUA_OK: { lua_Debug ar; - if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */ - lua_pushliteral(L, "normal"); /* it is running */ + if (lua_getstack(co, 0, &ar)) /* does it have frames? */ + return COS_NORM; /* it is running */ else if (lua_gettop(co) == 0) - lua_pushliteral(L, "dead"); + return COS_DEAD; else - lua_pushliteral(L, "suspended"); /* initial state */ - break; + return COS_YIELD; /* initial state */ } default: /* some error occurred */ - lua_pushliteral(L, "dead"); - break; + return COS_DEAD; } } +} + + +static int luaB_costatus (lua_State *L) { + lua_State *co = getco(L); + lua_pushstring(L, statname[auxstatus(L, co)]); return 1; } static int luaB_yieldable (lua_State *L) { - lua_pushboolean(L, lua_isyieldable(L)); + lua_State *co = lua_isnone(L, 1) ? L : getco(L); + lua_pushboolean(L, lua_isyieldable(co)); return 1; } @@ -148,6 +167,28 @@ static int luaB_corunning (lua_State *L) { } +static int luaB_close (lua_State *L) { + lua_State *co = getco(L); + int status = auxstatus(L, co); + switch (status) { + case COS_DEAD: case COS_YIELD: { + status = lua_closethread(co, L); + if (status == LUA_OK) { + lua_pushboolean(L, 1); + return 1; + } + else { + lua_pushboolean(L, 0); + lua_xmove(co, L, 1); /* move error message */ + return 2; + } + } + default: /* normal or running coroutine */ + return luaL_error(L, "cannot close a %s coroutine", statname[status]); + } +} + + static const luaL_Reg co_funcs[] = { {"create", luaB_cocreate}, {"resume", luaB_coresume}, @@ -156,6 +197,7 @@ static const luaL_Reg co_funcs[] = { {"wrap", luaB_cowrap}, {"yield", luaB_yield}, {"isyieldable", luaB_yieldable}, + {"close", luaB_close}, {NULL, NULL} }; diff --git a/libs/lua/lctype.c b/libs/lua/lctype.c index ae9367e6..95422809 100644 --- a/libs/lua/lctype.c +++ b/libs/lua/lctype.c @@ -1,5 +1,5 @@ /* -** $Id: lctype.c,v 1.12 2014/11/02 19:19:04 roberto Exp $ +** $Id: lctype.c $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ @@ -16,6 +16,15 @@ #include + +#if defined (LUA_UCID) /* accept UniCode IDentifiers? */ +/* consider all non-ascii codepoints to be alphabetic */ +#define NONA 0x01 +#else +#define NONA 0x00 /* default */ +#endif + + LUAI_DDEF const lu_byte luai_ctype_[UCHAR_MAX + 2] = { 0x00, /* EOZ */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0. */ @@ -34,22 +43,22 @@ LUAI_DDEF const lu_byte luai_ctype_[UCHAR_MAX + 2] = { 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 7. */ 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 8. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 9. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* a. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* d. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* e. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* f. */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 8. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 9. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* a. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* b. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + 0x00, 0x00, NONA, NONA, NONA, NONA, NONA, NONA, /* c. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* d. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* e. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, 0x00, 0x00, 0x00, /* f. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif /* } */ diff --git a/libs/lua/lctype.h b/libs/lua/lctype.h index 99c7d122..864e1901 100644 --- a/libs/lua/lctype.h +++ b/libs/lua/lctype.h @@ -1,5 +1,5 @@ /* -** $Id: lctype.h,v 1.12 2011/07/15 12:50:29 roberto Exp $ +** $Id: lctype.h $ ** 'ctype' functions for Lua ** See Copyright Notice in lua.h */ @@ -13,7 +13,7 @@ /* ** WARNING: the functions defined here do not necessarily correspond ** to the similar functions in the standard C ctype.h. They are -** optimized for the specific needs of Lua +** optimized for the specific needs of Lua. */ #if !defined(LUA_USE_CTYPE) @@ -61,14 +61,20 @@ #define lisprint(c) testprop(c, MASK(PRINTBIT)) #define lisxdigit(c) testprop(c, MASK(XDIGITBIT)) + /* -** this 'ltolower' only works for alphabetic characters +** In ASCII, this 'ltolower' is correct for alphabetic characters and +** for '.'. That is enough for Lua needs. ('check_exp' ensures that +** the character either is an upper-case letter or is unchanged by +** the transformation, which holds for lower-case letters and '.'.) */ -#define ltolower(c) ((c) | ('A' ^ 'a')) +#define ltolower(c) \ + check_exp(('A' <= (c) && (c) <= 'Z') || (c) == ((c) | ('A' ^ 'a')), \ + (c) | ('A' ^ 'a')) -/* two more entries for 0 and -1 (EOZ) */ -LUAI_DDEC const lu_byte luai_ctype_[UCHAR_MAX + 2]; +/* one entry for each character and for -1 (EOZ) */ +LUAI_DDEC(const lu_byte luai_ctype_[UCHAR_MAX + 2];) #else /* }{ */ diff --git a/libs/lua/ldblib.c b/libs/lua/ldblib.c index 91514584..6dcbaa98 100644 --- a/libs/lua/ldblib.c +++ b/libs/lua/ldblib.c @@ -1,5 +1,5 @@ /* -** $Id: ldblib.c,v 1.149 2015/02/19 17:06:21 roberto Exp $ +** $Id: ldblib.c $ ** Interface from Lua to its debug API ** See Copyright Notice in lua.h */ @@ -21,19 +21,19 @@ /* -** The hook table at registry[&HOOKKEY] maps threads to their current -** hook function. (We only need the unique address of 'HOOKKEY'.) +** The hook table at registry[HOOKKEY] maps threads to their current +** hook function. */ -static const int HOOKKEY = 0; +static const char *const HOOKKEY = "_HOOKKEY"; /* -** If L1 != L, L1 can be in any state, and therefore there is no -** garanties about its stack space; any push in L1 must be +** If L1 != L, L1 can be in any state, and therefore there are no +** guarantees about its stack space; any push in L1 must be ** checked. */ static void checkstack (lua_State *L, lua_State *L1, int n) { - if (L != L1 && !lua_checkstack(L1, n)) + if (l_unlikely(L != L1 && !lua_checkstack(L1, n))) luaL_error(L, "stack overflow"); } @@ -55,8 +55,7 @@ static int db_getmetatable (lua_State *L) { static int db_setmetatable (lua_State *L) { int t = lua_type(L, 2); - luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, - "nil or table expected"); + luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table"); lua_settop(L, 2); lua_setmetatable(L, 1); return 1; /* return 1st argument */ @@ -64,19 +63,24 @@ static int db_setmetatable (lua_State *L) { static int db_getuservalue (lua_State *L) { + int n = (int)luaL_optinteger(L, 2, 1); if (lua_type(L, 1) != LUA_TUSERDATA) - lua_pushnil(L); - else - lua_getuservalue(L, 1); + luaL_pushfail(L); + else if (lua_getiuservalue(L, 1, n) != LUA_TNONE) { + lua_pushboolean(L, 1); + return 2; + } return 1; } static int db_setuservalue (lua_State *L) { + int n = (int)luaL_optinteger(L, 3, 1); luaL_checktype(L, 1, LUA_TUSERDATA); luaL_checkany(L, 2); lua_settop(L, 2); - lua_setuservalue(L, 1); + if (!lua_setiuservalue(L, 1, n)) + luaL_pushfail(L); return 1; } @@ -146,8 +150,9 @@ static int db_getinfo (lua_State *L) { lua_Debug ar; int arg; lua_State *L1 = getthread(L, &arg); - const char *options = luaL_optstring(L, arg+2, "flnStu"); + const char *options = luaL_optstring(L, arg+2, "flnSrtu"); checkstack(L, L1, 3); + luaL_argcheck(L, options[0] != '>', arg + 2, "invalid option '>'"); if (lua_isfunction(L, arg + 1)) { /* info about a function? */ options = lua_pushfstring(L, ">%s", options); /* add '>' to 'options' */ lua_pushvalue(L, arg + 1); /* move function to 'L1' stack */ @@ -155,7 +160,7 @@ static int db_getinfo (lua_State *L) { } else { /* stack level */ if (!lua_getstack(L1, (int)luaL_checkinteger(L, arg + 1), &ar)) { - lua_pushnil(L); /* level out of range */ + luaL_pushfail(L); /* level out of range */ return 1; } } @@ -163,7 +168,8 @@ static int db_getinfo (lua_State *L) { return luaL_argerror(L, arg+2, "invalid option"); lua_newtable(L); /* table to collect results */ if (strchr(options, 'S')) { - settabss(L, "source", ar.source); + lua_pushlstring(L, ar.source, ar.srclen); + lua_setfield(L, -2, "source"); settabss(L, "short_src", ar.short_src); settabsi(L, "linedefined", ar.linedefined); settabsi(L, "lastlinedefined", ar.lastlinedefined); @@ -180,6 +186,10 @@ static int db_getinfo (lua_State *L) { settabss(L, "name", ar.name); settabss(L, "namewhat", ar.namewhat); } + if (strchr(options, 'r')) { + settabsi(L, "ftransfer", ar.ftransfer); + settabsi(L, "ntransfer", ar.ntransfer); + } if (strchr(options, 't')) settabsb(L, "istailcall", ar.istailcall); if (strchr(options, 'L')) @@ -193,8 +203,6 @@ static int db_getinfo (lua_State *L) { static int db_getlocal (lua_State *L) { int arg; lua_State *L1 = getthread(L, &arg); - lua_Debug ar; - const char *name; int nvar = (int)luaL_checkinteger(L, arg + 2); /* local-variable index */ if (lua_isfunction(L, arg + 1)) { /* function argument? */ lua_pushvalue(L, arg + 1); /* push function */ @@ -202,8 +210,10 @@ static int db_getlocal (lua_State *L) { return 1; /* return only name (there is no value) */ } else { /* stack-level argument */ + lua_Debug ar; + const char *name; int level = (int)luaL_checkinteger(L, arg + 1); - if (!lua_getstack(L1, level, &ar)) /* out of range? */ + if (l_unlikely(!lua_getstack(L1, level, &ar))) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); checkstack(L, L1, 1); name = lua_getlocal(L1, &ar, nvar); @@ -214,7 +224,7 @@ static int db_getlocal (lua_State *L) { return 2; } else { - lua_pushnil(L); /* no name (nor value) */ + luaL_pushfail(L); /* no name (nor value) */ return 1; } } @@ -228,7 +238,7 @@ static int db_setlocal (lua_State *L) { lua_Debug ar; int level = (int)luaL_checkinteger(L, arg + 1); int nvar = (int)luaL_checkinteger(L, arg + 2); - if (!lua_getstack(L1, level, &ar)) /* out of range? */ + if (l_unlikely(!lua_getstack(L1, level, &ar))) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); luaL_checkany(L, arg+3); lua_settop(L, arg+3); @@ -272,25 +282,33 @@ static int db_setupvalue (lua_State *L) { ** Check whether a given upvalue from a given closure exists and ** returns its index */ -static int checkupval (lua_State *L, int argf, int argnup) { +static void *checkupval (lua_State *L, int argf, int argnup, int *pnup) { + void *id; int nup = (int)luaL_checkinteger(L, argnup); /* upvalue index */ luaL_checktype(L, argf, LUA_TFUNCTION); /* closure */ - luaL_argcheck(L, (lua_getupvalue(L, argf, nup) != NULL), argnup, - "invalid upvalue index"); - return nup; + id = lua_upvalueid(L, argf, nup); + if (pnup) { + luaL_argcheck(L, id != NULL, argnup, "invalid upvalue index"); + *pnup = nup; + } + return id; } static int db_upvalueid (lua_State *L) { - int n = checkupval(L, 1, 2); - lua_pushlightuserdata(L, lua_upvalueid(L, 1, n)); + void *id = checkupval(L, 1, 2, NULL); + if (id != NULL) + lua_pushlightuserdata(L, id); + else + luaL_pushfail(L); return 1; } static int db_upvaluejoin (lua_State *L) { - int n1 = checkupval(L, 1, 2); - int n2 = checkupval(L, 3, 4); + int n1, n2; + checkupval(L, 1, 2, &n1); + checkupval(L, 3, 4, &n2); luaL_argcheck(L, !lua_iscfunction(L, 1), 1, "Lua function expected"); luaL_argcheck(L, !lua_iscfunction(L, 3), 3, "Lua function expected"); lua_upvaluejoin(L, 1, n1, 3, n2); @@ -305,7 +323,7 @@ static int db_upvaluejoin (lua_State *L) { static void hookf (lua_State *L, lua_Debug *ar) { static const char *const hooknames[] = {"call", "return", "line", "count", "tail call"}; - lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY); + lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY); lua_pushthread(L); if (lua_rawget(L, -2) == LUA_TFUNCTION) { /* is there a hook function? */ lua_pushstring(L, hooknames[(int)ar->event]); /* push event name */ @@ -358,14 +376,12 @@ static int db_sethook (lua_State *L) { count = (int)luaL_optinteger(L, arg + 3, 0); func = hookf; mask = makemask(smask, count); } - if (lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY) == LUA_TNIL) { - lua_createtable(L, 0, 2); /* create a hook table */ - lua_pushvalue(L, -1); - lua_rawsetp(L, LUA_REGISTRYINDEX, &HOOKKEY); /* set it in position */ - lua_pushstring(L, "k"); + if (!luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY)) { + /* table just created; initialize it */ + lua_pushliteral(L, "k"); lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */ lua_pushvalue(L, -1); - lua_setmetatable(L, -2); /* setmetatable(hooktable) = hooktable */ + lua_setmetatable(L, -2); /* metatable(hooktable) = hooktable */ } checkstack(L, L1, 1); lua_pushthread(L1); lua_xmove(L1, L, 1); /* key (thread) */ @@ -382,12 +398,14 @@ static int db_gethook (lua_State *L) { char buff[5]; int mask = lua_gethookmask(L1); lua_Hook hook = lua_gethook(L1); - if (hook == NULL) /* no hook? */ - lua_pushnil(L); + if (hook == NULL) { /* no hook? */ + luaL_pushfail(L); + return 1; + } else if (hook != hookf) /* external hook? */ lua_pushliteral(L, "external hook"); else { /* hook table must exist */ - lua_rawgetp(L, LUA_REGISTRYINDEX, &HOOKKEY); + lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY); checkstack(L, L1, 1); lua_pushthread(L1); lua_xmove(L1, L, 1); lua_rawget(L, -2); /* 1st result = hooktable[L1] */ @@ -403,12 +421,12 @@ static int db_debug (lua_State *L) { for (;;) { char buffer[250]; lua_writestringerror("%s", "lua_debug> "); - if (fgets(buffer, sizeof(buffer), stdin) == 0 || + if (fgets(buffer, sizeof(buffer), stdin) == NULL || strcmp(buffer, "cont\n") == 0) return 0; if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || lua_pcall(L, 0, 0, 0)) - lua_writestringerror("%s\n", lua_tostring(L, -1)); + lua_writestringerror("%s\n", luaL_tolstring(L, -1, NULL)); lua_settop(L, 0); /* remove eventual returns */ } } @@ -428,6 +446,14 @@ static int db_traceback (lua_State *L) { } +static int db_setcstacklimit (lua_State *L) { + int limit = (int)luaL_checkinteger(L, 1); + int res = lua_setcstacklimit(L, limit); + lua_pushinteger(L, res); + return 1; +} + + static const luaL_Reg dblib[] = { {"debug", db_debug}, {"getuservalue", db_getuservalue}, @@ -445,6 +471,7 @@ static const luaL_Reg dblib[] = { {"setmetatable", db_setmetatable}, {"setupvalue", db_setupvalue}, {"traceback", db_traceback}, + {"setcstacklimit", db_setcstacklimit}, {NULL, NULL} }; diff --git a/libs/lua/ldebug.c b/libs/lua/ldebug.c index f76582c5..591b3528 100644 --- a/libs/lua/ldebug.c +++ b/libs/lua/ldebug.c @@ -1,5 +1,5 @@ /* -** $Id: ldebug.c,v 2.115 2015/05/22 17:45:56 roberto Exp $ +** $Id: ldebug.c $ ** Debug Interface ** See Copyright Notice in lua.h */ @@ -31,14 +31,11 @@ -#define noLuaClosure(f) ((f) == NULL || (f)->c.tt == LUA_TCCL) +#define LuaClosure(f) ((f) != NULL && (f)->c.tt == LUA_VLCL) -/* Active Lua function (given call info) */ -#define ci_func(ci) (clLvalue((ci)->func)) - - -static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); +static const char *funcnamefromcall (lua_State *L, CallInfo *ci, + const char **name); static int currentpc (CallInfo *ci) { @@ -47,41 +44,101 @@ static int currentpc (CallInfo *ci) { } -static int currentline (CallInfo *ci) { - return getfuncline(ci_func(ci)->p, currentpc(ci)); +/* +** Get a "base line" to find the line corresponding to an instruction. +** Base lines are regularly placed at MAXIWTHABS intervals, so usually +** an integer division gets the right place. When the source file has +** large sequences of empty/comment lines, it may need extra entries, +** so the original estimate needs a correction. +** If the original estimate is -1, the initial 'if' ensures that the +** 'while' will run at least once. +** The assertion that the estimate is a lower bound for the correct base +** is valid as long as the debug info has been generated with the same +** value for MAXIWTHABS or smaller. (Previous releases use a little +** smaller value.) +*/ +static int getbaseline (const Proto *f, int pc, int *basepc) { + if (f->sizeabslineinfo == 0 || pc < f->abslineinfo[0].pc) { + *basepc = -1; /* start from the beginning */ + return f->linedefined; + } + else { + int i = cast_uint(pc) / MAXIWTHABS - 1; /* get an estimate */ + /* estimate must be a lower bound of the correct base */ + lua_assert(i < 0 || + (i < f->sizeabslineinfo && f->abslineinfo[i].pc <= pc)); + while (i + 1 < f->sizeabslineinfo && pc >= f->abslineinfo[i + 1].pc) + i++; /* low estimate; adjust it */ + *basepc = f->abslineinfo[i].pc; + return f->abslineinfo[i].line; + } } /* -** If function yielded, its 'func' can be in the 'extra' field. The -** next function restores 'func' to its correct value for debugging -** purposes. (It exchanges 'func' and 'extra'; so, when called again, -** after debugging, it also "re-restores" ** 'func' to its altered value. +** Get the line corresponding to instruction 'pc' in function 'f'; +** first gets a base line and from there does the increments until +** the desired instruction. */ -static void swapextra (lua_State *L) { - if (L->status == LUA_YIELD) { - CallInfo *ci = L->ci; /* get function that yielded */ - StkId temp = ci->func; /* exchange its 'func' and 'extra' values */ - ci->func = restorestack(L, ci->extra); - ci->extra = savestack(L, temp); +int luaG_getfuncline (const Proto *f, int pc) { + if (f->lineinfo == NULL) /* no debug information? */ + return -1; + else { + int basepc; + int baseline = getbaseline(f, pc, &basepc); + while (basepc++ < pc) { /* walk until given instruction */ + lua_assert(f->lineinfo[basepc] != ABSLINEINFO); + baseline += f->lineinfo[basepc]; /* correct line */ + } + return baseline; } } +static int getcurrentline (CallInfo *ci) { + return luaG_getfuncline(ci_func(ci)->p, currentpc(ci)); +} + + +/* +** Set 'trap' for all active Lua frames. +** This function can be called during a signal, under "reasonable" +** assumptions. A new 'ci' is completely linked in the list before it +** becomes part of the "active" list, and we assume that pointers are +** atomic; see comment in next function. +** (A compiler doing interprocedural optimizations could, theoretically, +** reorder memory writes in such a way that the list could be +** temporarily broken while inserting a new element. We simply assume it +** has no good reasons to do that.) +*/ +static void settraps (CallInfo *ci) { + for (; ci != NULL; ci = ci->previous) + if (isLua(ci)) + ci->u.l.trap = 1; +} + + /* -** this function can be called asynchronous (e.g. during a signal) +** This function can be called during a signal, under "reasonable" +** assumptions. +** Fields 'basehookcount' and 'hookcount' (set by 'resethookcount') +** are for debug only, and it is no problem if they get arbitrary +** values (causes at most one wrong hook call). 'hookmask' is an atomic +** value. We assume that pointers are atomic too (e.g., gcc ensures that +** for all platforms where it runs). Moreover, 'hook' is always checked +** before being called (see 'luaD_hook'). */ LUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { if (func == NULL || mask == 0) { /* turn off hooks? */ mask = 0; func = NULL; } - if (isLua(L->ci)) - L->oldpc = L->ci->u.l.savedpc; L->hook = func; L->basehookcount = count; resethookcount(L); L->hookmask = cast_byte(mask); + if (mask) + settraps(L->ci); /* to trace inside 'luaV_execute' */ } @@ -117,7 +174,7 @@ LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) { } -static const char *upvalname (Proto *p, int uv) { +static const char *upvalname (const Proto *p, int uv) { TString *s = check_exp(uv < p->sizeupvalues, p->upvalues[uv].name); if (s == NULL) return "?"; else return getstr(s); @@ -125,38 +182,37 @@ static const char *upvalname (Proto *p, int uv) { static const char *findvararg (CallInfo *ci, int n, StkId *pos) { - int nparams = clLvalue(ci->func)->p->numparams; - if (n >= cast_int(ci->u.l.base - ci->func) - nparams) - return NULL; /* no such vararg */ - else { - *pos = ci->func + nparams + n; - return "(*vararg)"; /* generic name for any vararg */ + if (clLvalue(s2v(ci->func.p))->p->is_vararg) { + int nextra = ci->u.l.nextraargs; + if (n >= -nextra) { /* 'n' is negative */ + *pos = ci->func.p - nextra - (n + 1); + return "(vararg)"; /* generic name for any vararg */ + } } + return NULL; /* no such vararg */ } -static const char *findlocal (lua_State *L, CallInfo *ci, int n, - StkId *pos) { +const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, StkId *pos) { + StkId base = ci->func.p + 1; const char *name = NULL; - StkId base; if (isLua(ci)) { if (n < 0) /* access to vararg values? */ - return findvararg(ci, -n, pos); - else { - base = ci->u.l.base; + return findvararg(ci, n, pos); + else name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci)); - } } - else - base = ci->func + 1; if (name == NULL) { /* no 'standard' name? */ - StkId limit = (ci == L->ci) ? L->top : ci->next->func; - if (limit - base >= n && n > 0) /* is 'n' inside 'ci' stack? */ - name = "(*temporary)"; /* generic name for any valid slot */ + StkId limit = (ci == L->ci) ? L->top.p : ci->next->func.p; + if (limit - base >= n && n > 0) { /* is 'n' inside 'ci' stack? */ + /* generic name for any valid slot */ + name = isLua(ci) ? "(temporary)" : "(C temporary)"; + } else return NULL; /* no name */ } - *pos = base + (n - 1); + if (pos) + *pos = base + (n - 1); return name; } @@ -164,22 +220,20 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n, LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { const char *name; lua_lock(L); - swapextra(L); if (ar == NULL) { /* information about non-active function? */ - if (!isLfunction(L->top - 1)) /* not a Lua function? */ + if (!isLfunction(s2v(L->top.p - 1))) /* not a Lua function? */ name = NULL; else /* consider live variables at function start (parameters) */ - name = luaF_getlocalname(clLvalue(L->top - 1)->p, n, 0); + name = luaF_getlocalname(clLvalue(s2v(L->top.p - 1))->p, n, 0); } else { /* active function; get information through 'ar' */ StkId pos = NULL; /* to avoid warnings */ - name = findlocal(L, ar->i_ci, n, &pos); + name = luaG_findlocal(L, ar->i_ci, n, &pos); if (name) { - setobj2s(L, L->top, pos); + setobjs2s(L, L->top.p, pos); api_incr_top(L); } } - swapextra(L); lua_unlock(L); return name; } @@ -189,55 +243,89 @@ LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { StkId pos = NULL; /* to avoid warnings */ const char *name; lua_lock(L); - swapextra(L); - name = findlocal(L, ar->i_ci, n, &pos); + name = luaG_findlocal(L, ar->i_ci, n, &pos); if (name) { - setobjs2s(L, pos, L->top - 1); - L->top--; /* pop value */ + setobjs2s(L, pos, L->top.p - 1); + L->top.p--; /* pop value */ } - swapextra(L); lua_unlock(L); return name; } static void funcinfo (lua_Debug *ar, Closure *cl) { - if (noLuaClosure(cl)) { + if (!LuaClosure(cl)) { ar->source = "=[C]"; + ar->srclen = LL("=[C]"); ar->linedefined = -1; ar->lastlinedefined = -1; ar->what = "C"; } else { - Proto *p = cl->l.p; - ar->source = p->source ? getstr(p->source) : "=?"; + const Proto *p = cl->l.p; + if (p->source) { + ar->source = getstr(p->source); + ar->srclen = tsslen(p->source); + } + else { + ar->source = "=?"; + ar->srclen = LL("=?"); + } ar->linedefined = p->linedefined; ar->lastlinedefined = p->lastlinedefined; ar->what = (ar->linedefined == 0) ? "main" : "Lua"; } - luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); + luaO_chunkid(ar->short_src, ar->source, ar->srclen); +} + + +static int nextline (const Proto *p, int currentline, int pc) { + if (p->lineinfo[pc] != ABSLINEINFO) + return currentline + p->lineinfo[pc]; + else + return luaG_getfuncline(p, pc); } static void collectvalidlines (lua_State *L, Closure *f) { - if (noLuaClosure(f)) { - setnilvalue(L->top); + if (!LuaClosure(f)) { + setnilvalue(s2v(L->top.p)); api_incr_top(L); } else { - int i; - TValue v; - int *lineinfo = f->l.p->lineinfo; + const Proto *p = f->l.p; + int currentline = p->linedefined; Table *t = luaH_new(L); /* new table to store active lines */ - sethvalue(L, L->top, t); /* push it on stack */ + sethvalue2s(L, L->top.p, t); /* push it on stack */ api_incr_top(L); - setbvalue(&v, 1); /* boolean 'true' to be the value of all indices */ - for (i = 0; i < f->l.p->sizelineinfo; i++) /* for all lines with code */ - luaH_setint(L, t, lineinfo[i], &v); /* table[line] = true */ + if (p->lineinfo != NULL) { /* proto with debug information? */ + int i; + TValue v; + setbtvalue(&v); /* boolean 'true' to be the value of all indices */ + if (!p->is_vararg) /* regular function? */ + i = 0; /* consider all instructions */ + else { /* vararg function */ + lua_assert(GET_OPCODE(p->code[0]) == OP_VARARGPREP); + currentline = nextline(p, currentline, 0); + i = 1; /* skip first instruction (OP_VARARGPREP) */ + } + for (; i < p->sizelineinfo; i++) { /* for each instruction */ + currentline = nextline(p, currentline, i); /* get its line */ + luaH_setint(L, t, currentline, &v); /* table[line] = true */ + } + } } } +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { + /* calling function is a known function? */ + if (ci != NULL && !(ci->callstatus & CIST_TAIL)) + return funcnamefromcall(L, ci->previous, name); + else return NULL; /* no way to find a name */ +} + + static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, Closure *f, CallInfo *ci) { int status = 1; @@ -248,12 +336,12 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, break; } case 'l': { - ar->currentline = (ci && isLua(ci)) ? currentline(ci) : -1; + ar->currentline = (ci && isLua(ci)) ? getcurrentline(ci) : -1; break; } case 'u': { ar->nups = (f == NULL) ? 0 : f->c.nupvalues; - if (noLuaClosure(f)) { + if (!LuaClosure(f)) { ar->isvararg = 1; ar->nparams = 0; } @@ -268,17 +356,22 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, break; } case 'n': { - /* calling function is a known Lua function? */ - if (ci && !(ci->callstatus & CIST_TAIL) && isLua(ci->previous)) - ar->namewhat = getfuncname(L, ci->previous, &ar->name); - else - ar->namewhat = NULL; + ar->namewhat = getfuncname(L, ci, &ar->name); if (ar->namewhat == NULL) { ar->namewhat = ""; /* not found */ ar->name = NULL; } break; } + case 'r': { + if (ci == NULL || !(ci->callstatus & CIST_TRAN)) + ar->ftransfer = ar->ntransfer = 0; + else { + ar->ftransfer = ci->u2.transferinfo.ftransfer; + ar->ntransfer = ci->u2.transferinfo.ntransfer; + } + break; + } case 'L': case 'f': /* handled by lua_getinfo */ break; @@ -293,28 +386,26 @@ LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { int status; Closure *cl; CallInfo *ci; - StkId func; + TValue *func; lua_lock(L); - swapextra(L); if (*what == '>') { ci = NULL; - func = L->top - 1; + func = s2v(L->top.p - 1); api_check(L, ttisfunction(func), "function expected"); what++; /* skip the '>' */ - L->top--; /* pop function */ + L->top.p--; /* pop function */ } else { ci = ar->i_ci; - func = ci->func; - lua_assert(ttisfunction(ci->func)); + func = s2v(ci->func.p); + lua_assert(ttisfunction(func)); } cl = ttisclosure(func) ? clvalue(func) : NULL; status = auxgetinfo(L, what, ar, cl, ci); if (strchr(what, 'f')) { - setobjs2s(L, L->top, func); + setobj2s(L, L->top.p, func); api_incr_top(L); } - swapextra(L); /* correct before option 'L', which can raise a mem. error */ if (strchr(what, 'L')) collectvalidlines(L, cl); lua_unlock(L); @@ -328,32 +419,6 @@ LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { ** ======================================================= */ -static const char *getobjname (Proto *p, int lastpc, int reg, - const char **name); - - -/* -** find a "name" for the RK value 'c' -*/ -static void kname (Proto *p, int pc, int c, const char **name) { - if (ISK(c)) { /* is 'c' a constant? */ - TValue *kvalue = &p->k[INDEXK(c)]; - if (ttisstring(kvalue)) { /* literal constant? */ - *name = svalue(kvalue); /* it is its own name */ - return; - } - /* else no reasonable name found */ - } - else { /* 'c' is a register */ - const char *what = getobjname(p, pc, c, name); /* search for 'c' */ - if (what && *what == 'c') { /* found a constant name? */ - return; /* 'name' already filled */ - } - /* else no reasonable name found */ - } - *name = "?"; /* no reasonable name found */ -} - static int filterpc (int pc, int jmptarget) { if (pc < jmptarget) /* is code conditional (inside a jump)? */ @@ -363,62 +428,78 @@ static int filterpc (int pc, int jmptarget) { /* -** try to find last instruction before 'lastpc' that modified register 'reg' +** Try to find last instruction before 'lastpc' that modified register 'reg'. */ -static int findsetreg (Proto *p, int lastpc, int reg) { +static int findsetreg (const Proto *p, int lastpc, int reg) { int pc; int setreg = -1; /* keep last instruction that changed 'reg' */ int jmptarget = 0; /* any code before this address is conditional */ + if (testMMMode(GET_OPCODE(p->code[lastpc]))) + lastpc--; /* previous instruction was not actually executed */ for (pc = 0; pc < lastpc; pc++) { Instruction i = p->code[pc]; OpCode op = GET_OPCODE(i); int a = GETARG_A(i); + int change; /* true if current instruction changed 'reg' */ switch (op) { - case OP_LOADNIL: { + case OP_LOADNIL: { /* set registers from 'a' to 'a+b' */ int b = GETARG_B(i); - if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */ - setreg = filterpc(pc, jmptarget); + change = (a <= reg && reg <= a + b); break; } - case OP_TFORCALL: { - if (reg >= a + 2) /* affect all regs above its base */ - setreg = filterpc(pc, jmptarget); + case OP_TFORCALL: { /* affect all regs above its base */ + change = (reg >= a + 2); break; } case OP_CALL: - case OP_TAILCALL: { - if (reg >= a) /* affect all registers above base */ - setreg = filterpc(pc, jmptarget); + case OP_TAILCALL: { /* affect all registers above base */ + change = (reg >= a); break; } - case OP_JMP: { - int b = GETARG_sBx(i); + case OP_JMP: { /* doesn't change registers, but changes 'jmptarget' */ + int b = GETARG_sJ(i); int dest = pc + 1 + b; - /* jump is forward and do not skip 'lastpc'? */ - if (pc < dest && dest <= lastpc) { - if (dest > jmptarget) - jmptarget = dest; /* update 'jmptarget' */ - } + /* jump does not skip 'lastpc' and is larger than current one? */ + if (dest <= lastpc && dest > jmptarget) + jmptarget = dest; /* update 'jmptarget' */ + change = 0; break; } - default: - if (testAMode(op) && reg == a) /* any instruction that set A */ - setreg = filterpc(pc, jmptarget); + default: /* any instruction that sets A */ + change = (testAMode(op) && reg == a); break; } + if (change) + setreg = filterpc(pc, jmptarget); } return setreg; } -static const char *getobjname (Proto *p, int lastpc, int reg, - const char **name) { - int pc; - *name = luaF_getlocalname(p, reg + 1, lastpc); +/* +** Find a "name" for the constant 'c'. +*/ +static const char *kname (const Proto *p, int index, const char **name) { + TValue *kvalue = &p->k[index]; + if (ttisstring(kvalue)) { + *name = getstr(tsvalue(kvalue)); + return "constant"; + } + else { + *name = "?"; + return NULL; + } +} + + +static const char *basicgetobjname (const Proto *p, int *ppc, int reg, + const char **name) { + int pc = *ppc; + *name = luaF_getlocalname(p, reg + 1, pc); if (*name) /* is a local? */ return "local"; /* else try symbolic execution */ - pc = findsetreg(p, lastpc, reg); + *ppc = pc = findsetreg(p, pc, reg); if (pc != -1) { /* could find instruction? */ Instruction i = p->code[pc]; OpCode op = GET_OPCODE(i); @@ -426,36 +507,92 @@ static const char *getobjname (Proto *p, int lastpc, int reg, case OP_MOVE: { int b = GETARG_B(i); /* move from 'b' to 'a' */ if (b < GETARG_A(i)) - return getobjname(p, pc, b, name); /* get name for 'b' */ + return basicgetobjname(p, ppc, b, name); /* get name for 'b' */ break; } - case OP_GETTABUP: - case OP_GETTABLE: { - int k = GETARG_C(i); /* key index */ - int t = GETARG_B(i); /* table index */ - const char *vn = (op == OP_GETTABLE) /* name of indexed variable */ - ? luaF_getlocalname(p, t + 1, pc) - : upvalname(p, t); - kname(p, pc, k, name); - return (vn && strcmp(vn, LUA_ENV) == 0) ? "global" : "field"; - } case OP_GETUPVAL: { *name = upvalname(p, GETARG_B(i)); return "upvalue"; } - case OP_LOADK: - case OP_LOADKX: { - int b = (op == OP_LOADK) ? GETARG_Bx(i) - : GETARG_Ax(p->code[pc + 1]); - if (ttisstring(&p->k[b])) { - *name = svalue(&p->k[b]); - return "constant"; - } - break; + case OP_LOADK: return kname(p, GETARG_Bx(i), name); + case OP_LOADKX: return kname(p, GETARG_Ax(p->code[pc + 1]), name); + default: break; + } + } + return NULL; /* could not find reasonable name */ +} + + +/* +** Find a "name" for the register 'c'. +*/ +static void rname (const Proto *p, int pc, int c, const char **name) { + const char *what = basicgetobjname(p, &pc, c, name); /* search for 'c' */ + if (!(what && *what == 'c')) /* did not find a constant name? */ + *name = "?"; +} + + +/* +** Find a "name" for a 'C' value in an RK instruction. +*/ +static void rkname (const Proto *p, int pc, Instruction i, const char **name) { + int c = GETARG_C(i); /* key index */ + if (GETARG_k(i)) /* is 'c' a constant? */ + kname(p, c, name); + else /* 'c' is a register */ + rname(p, pc, c, name); +} + + +/* +** Check whether table being indexed by instruction 'i' is the +** environment '_ENV' +*/ +static const char *isEnv (const Proto *p, int pc, Instruction i, int isup) { + int t = GETARG_B(i); /* table index */ + const char *name; /* name of indexed variable */ + if (isup) /* is 't' an upvalue? */ + name = upvalname(p, t); + else /* 't' is a register */ + basicgetobjname(p, &pc, t, &name); + return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field"; +} + + +/* +** Extend 'basicgetobjname' to handle table accesses +*/ +static const char *getobjname (const Proto *p, int lastpc, int reg, + const char **name) { + const char *kind = basicgetobjname(p, &lastpc, reg, name); + if (kind != NULL) + return kind; + else if (lastpc != -1) { /* could find instruction? */ + Instruction i = p->code[lastpc]; + OpCode op = GET_OPCODE(i); + switch (op) { + case OP_GETTABUP: { + int k = GETARG_C(i); /* key index */ + kname(p, k, name); + return isEnv(p, lastpc, i, 1); } - case OP_SELF: { + case OP_GETTABLE: { + int k = GETARG_C(i); /* key index */ + rname(p, lastpc, k, name); + return isEnv(p, lastpc, i, 0); + } + case OP_GETI: { + *name = "integer index"; + return "field"; + } + case OP_GETFIELD: { int k = GETARG_C(i); /* key index */ - kname(p, pc, k, name); + kname(p, k, name); + return isEnv(p, lastpc, i, 0); + } + case OP_SELF: { + rkname(p, lastpc, i, name); return "method"; } default: break; /* go through to return NULL */ @@ -465,35 +602,34 @@ static const char *getobjname (Proto *p, int lastpc, int reg, } -static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { - TMS tm = (TMS)0; /* to avoid warnings */ - Proto *p = ci_func(ci)->p; /* calling function */ - int pc = currentpc(ci); /* calling instruction index */ +/* +** Try to find a name for a function based on the code that called it. +** (Only works when function was called by a Lua function.) +** Returns what the name is (e.g., "for iterator", "method", +** "metamethod") and sets '*name' to point to the name. +*/ +static const char *funcnamefromcode (lua_State *L, const Proto *p, + int pc, const char **name) { + TMS tm = (TMS)0; /* (initial value avoids warnings) */ Instruction i = p->code[pc]; /* calling instruction */ - if (ci->callstatus & CIST_HOOKED) { /* was it called inside a hook? */ - *name = "?"; - return "hook"; - } switch (GET_OPCODE(i)) { case OP_CALL: - case OP_TAILCALL: /* get function name */ - return getobjname(p, pc, GETARG_A(i), name); + case OP_TAILCALL: + return getobjname(p, pc, GETARG_A(i), name); /* get function name */ case OP_TFORCALL: { /* for iterator */ *name = "for iterator"; return "for iterator"; } - /* all other instructions can call only through metamethods */ + /* other instructions can do calls through metamethods */ case OP_SELF: case OP_GETTABUP: case OP_GETTABLE: + case OP_GETI: case OP_GETFIELD: tm = TM_INDEX; break; - case OP_SETTABUP: case OP_SETTABLE: + case OP_SETTABUP: case OP_SETTABLE: case OP_SETI: case OP_SETFIELD: tm = TM_NEWINDEX; break; - case OP_ADD: case OP_SUB: case OP_MUL: case OP_MOD: - case OP_POW: case OP_DIV: case OP_IDIV: case OP_BAND: - case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: { - int offset = cast_int(GET_OPCODE(i)) - cast_int(OP_ADD); /* ORDER OP */ - tm = cast(TMS, offset + cast_int(TM_ADD)); /* ORDER TM */ + case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: { + tm = cast(TMS, GETARG_C(i)); break; } case OP_UNM: tm = TM_UNM; break; @@ -501,26 +637,55 @@ static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { case OP_LEN: tm = TM_LEN; break; case OP_CONCAT: tm = TM_CONCAT; break; case OP_EQ: tm = TM_EQ; break; - case OP_LT: tm = TM_LT; break; - case OP_LE: tm = TM_LE; break; - default: lua_assert(0); /* other instructions cannot call a function */ + /* no cases for OP_EQI and OP_EQK, as they don't call metamethods */ + case OP_LT: case OP_LTI: case OP_GTI: tm = TM_LT; break; + case OP_LE: case OP_LEI: case OP_GEI: tm = TM_LE; break; + case OP_CLOSE: case OP_RETURN: tm = TM_CLOSE; break; + default: + return NULL; /* cannot find a reasonable name */ } - *name = getstr(G(L)->tmname[tm]); + *name = getshrstr(G(L)->tmname[tm]) + 2; return "metamethod"; } + +/* +** Try to find a name for a function based on how it was called. +*/ +static const char *funcnamefromcall (lua_State *L, CallInfo *ci, + const char **name) { + if (ci->callstatus & CIST_HOOKED) { /* was it called inside a hook? */ + *name = "?"; + return "hook"; + } + else if (ci->callstatus & CIST_FIN) { /* was it called as a finalizer? */ + *name = "__gc"; + return "metamethod"; /* report it as such */ + } + else if (isLua(ci)) + return funcnamefromcode(L, ci_func(ci)->p, currentpc(ci), name); + else + return NULL; +} + /* }====================================================== */ /* -** The subtraction of two potentially unrelated pointers is -** not ISO C, but it should not crash a program; the subsequent -** checks are ISO C and ensure a correct result. +** Check whether pointer 'o' points to some value in the stack frame of +** the current function and, if so, returns its index. Because 'o' may +** not point to a value in this stack, we cannot compare it with the +** region boundaries (undefined behavior in ISO C). */ -static int isinstack (CallInfo *ci, const TValue *o) { - ptrdiff_t i = o - ci->u.l.base; - return (0 <= i && i < (ci->top - ci->u.l.base) && ci->u.l.base + i == o); +static int instack (CallInfo *ci, const TValue *o) { + int pos; + StkId base = ci->func.p + 1; + for (pos = 0; base + pos < ci->top.p; pos++) { + if (o == s2v(base + pos)) + return pos; + } + return -1; /* not found */ } @@ -534,7 +699,7 @@ static const char *getupvalname (CallInfo *ci, const TValue *o, LClosure *c = ci_func(ci); int i; for (i = 0; i < c->nupvalues; i++) { - if (c->upvals[i]->v == o) { + if (c->upvals[i]->v.p == o) { *name = upvalname(c->p, i); return "upvalue"; } @@ -543,23 +708,70 @@ static const char *getupvalname (CallInfo *ci, const TValue *o, } +static const char *formatvarinfo (lua_State *L, const char *kind, + const char *name) { + if (kind == NULL) + return ""; /* no information */ + else + return luaO_pushfstring(L, " (%s '%s')", kind, name); +} + +/* +** Build a string with a "description" for the value 'o', such as +** "variable 'x'" or "upvalue 'y'". +*/ static const char *varinfo (lua_State *L, const TValue *o) { - const char *name = NULL; /* to avoid warnings */ CallInfo *ci = L->ci; + const char *name = NULL; /* to avoid warnings */ const char *kind = NULL; if (isLua(ci)) { kind = getupvalname(ci, o, &name); /* check whether 'o' is an upvalue */ - if (!kind && isinstack(ci, o)) /* no? try a register */ - kind = getobjname(ci_func(ci)->p, currentpc(ci), - cast_int(o - ci->u.l.base), &name); + if (!kind) { /* not an upvalue? */ + int reg = instack(ci, o); /* try a register */ + if (reg >= 0) /* is 'o' a register? */ + kind = getobjname(ci_func(ci)->p, currentpc(ci), reg, &name); + } } - return (kind) ? luaO_pushfstring(L, " (%s '%s')", kind, name) : ""; + return formatvarinfo(L, kind, name); } +/* +** Raise a type error +*/ +static l_noret typeerror (lua_State *L, const TValue *o, const char *op, + const char *extra) { + const char *t = luaT_objtypename(L, o); + luaG_runerror(L, "attempt to %s a %s value%s", op, t, extra); +} + + +/* +** Raise a type error with "standard" information about the faulty +** object 'o' (using 'varinfo'). +*/ l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { - const char *t = objtypename(o); - luaG_runerror(L, "attempt to %s a %s value%s", op, t, varinfo(L, o)); + typeerror(L, o, op, varinfo(L, o)); +} + + +/* +** Raise an error for calling a non-callable object. Try to find a name +** for the object based on how it was called ('funcnamefromcall'); if it +** cannot get a name there, try 'varinfo'. +*/ +l_noret luaG_callerror (lua_State *L, const TValue *o) { + CallInfo *ci = L->ci; + const char *name = NULL; /* to avoid warnings */ + const char *kind = funcnamefromcall(L, ci, &name); + const char *extra = kind ? formatvarinfo(L, kind, name) : varinfo(L, o); + typeerror(L, o, "call", extra); +} + + +l_noret luaG_forerror (lua_State *L, const TValue *o, const char *what) { + luaG_runerror(L, "bad 'for' %s (number expected, got %s)", + what, luaT_objtypename(L, o)); } @@ -571,8 +783,7 @@ l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) { l_noret luaG_opinterror (lua_State *L, const TValue *p1, const TValue *p2, const char *msg) { - lua_Number temp; - if (!tonumber(p1, &temp)) /* first operand is wrong? */ + if (!ttisnumber(p1)) /* first operand is wrong? */ p2 = p1; /* now second is wrong */ luaG_typeerror(L, p2, msg); } @@ -583,16 +794,16 @@ l_noret luaG_opinterror (lua_State *L, const TValue *p1, */ l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) { lua_Integer temp; - if (!tointeger(p1, &temp)) + if (!luaV_tointegerns(p1, &temp, LUA_FLOORN2I)) p2 = p1; luaG_runerror(L, "number%s has no integer representation", varinfo(L, p2)); } l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { - const char *t1 = objtypename(p1); - const char *t2 = objtypename(p2); - if (t1 == t2) + const char *t1 = luaT_objtypename(L, p1); + const char *t2 = luaT_objtypename(L, p2); + if (strcmp(t1, t2) == 0) luaG_runerror(L, "attempt to compare two %s values", t1); else luaG_runerror(L, "attempt to compare %s with %s", t1, t2); @@ -604,7 +815,7 @@ const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, int line) { char buff[LUA_IDSIZE]; if (src) - luaO_chunkid(buff, getstr(src), LUA_IDSIZE); + luaO_chunkid(buff, getstr(src), tsslen(src)); else { /* no source available; use "?" instead */ buff[0] = '?'; buff[1] = '\0'; } @@ -615,10 +826,11 @@ const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, l_noret luaG_errormsg (lua_State *L) { if (L->errfunc != 0) { /* is there an error handling function? */ StkId errfunc = restorestack(L, L->errfunc); - setobjs2s(L, L->top, L->top - 1); /* move argument */ - setobjs2s(L, L->top - 1, errfunc); /* push function */ - L->top++; /* assume EXTRA_STACK */ - luaD_call(L, L->top - 2, 1, 0); /* call it */ + lua_assert(ttisfunction(s2v(errfunc))); + setobjs2s(L, L->top.p, L->top.p - 1); /* move argument */ + setobjs2s(L, L->top.p - 1, errfunc); /* push function */ + L->top.p++; /* assume EXTRA_STACK */ + luaD_callnoyield(L, L->top.p - 2, 1); /* call it */ } luaD_throw(L, LUA_ERRRUN); } @@ -628,44 +840,123 @@ l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { CallInfo *ci = L->ci; const char *msg; va_list argp; + luaC_checkGC(L); /* error message uses memory */ va_start(argp, fmt); msg = luaO_pushvfstring(L, fmt, argp); /* format message */ va_end(argp); - if (isLua(ci)) /* if Lua function, add source:line information */ - luaG_addinfo(L, msg, ci_func(ci)->p->source, currentline(ci)); + if (isLua(ci)) { /* if Lua function, add source:line information */ + luaG_addinfo(L, msg, ci_func(ci)->p->source, getcurrentline(ci)); + setobjs2s(L, L->top.p - 2, L->top.p - 1); /* remove 'msg' */ + L->top.p--; + } luaG_errormsg(L); } -void luaG_traceexec (lua_State *L) { +/* +** Check whether new instruction 'newpc' is in a different line from +** previous instruction 'oldpc'. More often than not, 'newpc' is only +** one or a few instructions after 'oldpc' (it must be after, see +** caller), so try to avoid calling 'luaG_getfuncline'. If they are +** too far apart, there is a good chance of a ABSLINEINFO in the way, +** so it goes directly to 'luaG_getfuncline'. +*/ +static int changedline (const Proto *p, int oldpc, int newpc) { + if (p->lineinfo == NULL) /* no debug information? */ + return 0; + if (newpc - oldpc < MAXIWTHABS / 2) { /* not too far apart? */ + int delta = 0; /* line difference */ + int pc = oldpc; + for (;;) { + int lineinfo = p->lineinfo[++pc]; + if (lineinfo == ABSLINEINFO) + break; /* cannot compute delta; fall through */ + delta += lineinfo; + if (pc == newpc) + return (delta != 0); /* delta computed successfully */ + } + } + /* either instructions are too far apart or there is an absolute line + info in the way; compute line difference explicitly */ + return (luaG_getfuncline(p, oldpc) != luaG_getfuncline(p, newpc)); +} + + +/* +** Traces Lua calls. If code is running the first instruction of a function, +** and function is not vararg, and it is not coming from an yield, +** calls 'luaD_hookcall'. (Vararg functions will call 'luaD_hookcall' +** after adjusting its variable arguments; otherwise, they could call +** a line/count hook before the call hook. Functions coming from +** an yield already called 'luaD_hookcall' before yielding.) +*/ +int luaG_tracecall (lua_State *L) { + CallInfo *ci = L->ci; + Proto *p = ci_func(ci)->p; + ci->u.l.trap = 1; /* ensure hooks will be checked */ + if (ci->u.l.savedpc == p->code) { /* first instruction (not resuming)? */ + if (p->is_vararg) + return 0; /* hooks will start at VARARGPREP instruction */ + else if (!(ci->callstatus & CIST_HOOKYIELD)) /* not yieded? */ + luaD_hookcall(L, ci); /* check 'call' hook */ + } + return 1; /* keep 'trap' on */ +} + + +/* +** Traces the execution of a Lua function. Called before the execution +** of each opcode, when debug is on. 'L->oldpc' stores the last +** instruction traced, to detect line changes. When entering a new +** function, 'npci' will be zero and will test as a new line whatever +** the value of 'oldpc'. Some exceptional conditions may return to +** a function without setting 'oldpc'. In that case, 'oldpc' may be +** invalid; if so, use zero as a valid value. (A wrong but valid 'oldpc' +** at most causes an extra call to a line hook.) +** This function is not "Protected" when called, so it should correct +** 'L->top.p' before calling anything that can run the GC. +*/ +int luaG_traceexec (lua_State *L, const Instruction *pc) { CallInfo *ci = L->ci; lu_byte mask = L->hookmask; - int counthook = ((mask & LUA_MASKCOUNT) && L->hookcount == 0); + const Proto *p = ci_func(ci)->p; + int counthook; + if (!(mask & (LUA_MASKLINE | LUA_MASKCOUNT))) { /* no hooks? */ + ci->u.l.trap = 0; /* don't need to stop again */ + return 0; /* turn off 'trap' */ + } + pc++; /* reference is always next instruction */ + ci->u.l.savedpc = pc; /* save 'pc' */ + counthook = (mask & LUA_MASKCOUNT) && (--L->hookcount == 0); if (counthook) resethookcount(L); /* reset count */ - if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ + else if (!(mask & LUA_MASKLINE)) + return 1; /* no line hook and count != 0; nothing to be done now */ + if (ci->callstatus & CIST_HOOKYIELD) { /* hook yielded last time? */ ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ - return; /* do not call hook again (VM yielded, so it did not move) */ + return 1; /* do not call hook again (VM yielded, so it did not move) */ } + if (!isIT(*(ci->u.l.savedpc - 1))) /* top not being used? */ + L->top.p = ci->top.p; /* correct top */ if (counthook) - luaD_hook(L, LUA_HOOKCOUNT, -1); /* call count hook */ + luaD_hook(L, LUA_HOOKCOUNT, -1, 0, 0); /* call count hook */ if (mask & LUA_MASKLINE) { - Proto *p = ci_func(ci)->p; - int npc = pcRel(ci->u.l.savedpc, p); - int newline = getfuncline(p, npc); - if (npc == 0 || /* call linehook when enter a new function, */ - ci->u.l.savedpc <= L->oldpc || /* when jump back (loop), or when */ - newline != getfuncline(p, pcRel(L->oldpc, p))) /* enter a new line */ - luaD_hook(L, LUA_HOOKLINE, newline); /* call line hook */ - } - L->oldpc = ci->u.l.savedpc; + /* 'L->oldpc' may be invalid; use zero in this case */ + int oldpc = (L->oldpc < p->sizecode) ? L->oldpc : 0; + int npci = pcRel(pc, p); + if (npci <= oldpc || /* call hook when jump back (loop), */ + changedline(p, oldpc, npci)) { /* or when enter new line */ + int newline = luaG_getfuncline(p, npci); + luaD_hook(L, LUA_HOOKLINE, newline, 0, 0); /* call line hook */ + } + L->oldpc = npci; /* 'pc' of last call to line hook */ + } if (L->status == LUA_YIELD) { /* did hook yield? */ if (counthook) L->hookcount = 1; /* undo decrement to zero */ - ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ - ci->func = L->top - 1; /* protect stack below results */ luaD_throw(L, LUA_YIELD); } + return 1; /* keep 'trap' on */ } diff --git a/libs/lua/ldebug.h b/libs/lua/ldebug.h index 0e31546b..2bfce3cb 100644 --- a/libs/lua/ldebug.h +++ b/libs/lua/ldebug.h @@ -1,5 +1,5 @@ /* -** $Id: ldebug.h,v 2.14 2015/05/22 17:45:56 roberto Exp $ +** $Id: ldebug.h $ ** Auxiliary functions from Debug Interface module ** See Copyright Notice in lua.h */ @@ -11,15 +11,39 @@ #include "lstate.h" -#define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1) +#define pcRel(pc, p) (cast_int((pc) - (p)->code) - 1) + + +/* Active Lua function (given call info) */ +#define ci_func(ci) (clLvalue(s2v((ci)->func.p))) -#define getfuncline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : -1) #define resethookcount(L) (L->hookcount = L->basehookcount) +/* +** mark for entries in 'lineinfo' array that has absolute information in +** 'abslineinfo' array +*/ +#define ABSLINEINFO (-0x80) + + +/* +** MAXimum number of successive Instructions WiTHout ABSolute line +** information. (A power of two allows fast divisions.) +*/ +#if !defined(MAXIWTHABS) +#define MAXIWTHABS 128 +#endif + +LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc); +LUAI_FUNC const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, + StkId *pos); LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *opname); +LUAI_FUNC l_noret luaG_callerror (lua_State *L, const TValue *o); +LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o, + const char *what); LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1, @@ -33,7 +57,8 @@ LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...); LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, int line); LUAI_FUNC l_noret luaG_errormsg (lua_State *L); -LUAI_FUNC void luaG_traceexec (lua_State *L); +LUAI_FUNC int luaG_traceexec (lua_State *L, const Instruction *pc); +LUAI_FUNC int luaG_tracecall (lua_State *L); #endif diff --git a/libs/lua/ldo.c b/libs/lua/ldo.c index 5c93a259..ea052950 100644 --- a/libs/lua/ldo.c +++ b/libs/lua/ldo.c @@ -1,5 +1,5 @@ /* -** $Id: ldo.c,v 2.138 2015/05/22 17:48:19 roberto Exp $ +** $Id: ldo.c $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ @@ -88,7 +88,7 @@ struct lua_longjmp { }; -static void seterrorobj (lua_State *L, int errcode, StkId oldtop) { +void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { switch (errcode) { case LUA_ERRMEM: { /* memory error? */ setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ @@ -98,12 +98,17 @@ static void seterrorobj (lua_State *L, int errcode, StkId oldtop) { setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); break; } + case LUA_OK: { /* special case only for closing upvalues */ + setnilvalue(s2v(oldtop)); /* no error message */ + break; + } default: { - setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ + lua_assert(errorstatus(errcode)); /* real error */ + setobjs2s(L, oldtop, L->top.p - 1); /* error message on current top */ break; } } - L->top = oldtop + 1; + L->top.p = oldtop + 1; } @@ -114,16 +119,13 @@ l_noret luaD_throw (lua_State *L, int errcode) { } else { /* thread has no error handler */ global_State *g = G(L); - L->status = cast_byte(errcode); /* mark it as dead */ + errcode = luaE_resetthread(L, errcode); /* close all upvalues */ if (g->mainthread->errorJmp) { /* main thread has a handler? */ - setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */ + setobjs2s(L, g->mainthread->top.p++, L->top.p - 1); /* copy error obj. */ luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ } else { /* no handler at all; abort */ if (g->panic) { /* panic function? */ - seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */ - if (L->ci->top < L->top) - L->ci->top = L->top; /* pushing msg. can break this invariant */ lua_unlock(L); g->panic(L); /* call panic function (last chance to jump out) */ } @@ -134,7 +136,7 @@ l_noret luaD_throw (lua_State *L, int errcode) { int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { - unsigned short oldnCcalls = L->nCcalls; + l_uint32 oldnCcalls = L->nCcalls; struct lua_longjmp lj; lj.status = LUA_OK; lj.previous = L->errorJmp; /* chain new error handler */ @@ -150,17 +152,45 @@ int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { /* }====================================================== */ -static void correctstack (lua_State *L, TValue *oldstack) { +/* +** {================================================================== +** Stack reallocation +** =================================================================== +*/ + + +/* +** Change all pointers to the stack into offsets. +*/ +static void relstack (lua_State *L) { + CallInfo *ci; + UpVal *up; + L->top.offset = savestack(L, L->top.p); + L->tbclist.offset = savestack(L, L->tbclist.p); + for (up = L->openupval; up != NULL; up = up->u.open.next) + up->v.offset = savestack(L, uplevel(up)); + for (ci = L->ci; ci != NULL; ci = ci->previous) { + ci->top.offset = savestack(L, ci->top.p); + ci->func.offset = savestack(L, ci->func.p); + } +} + + +/* +** Change back all offsets into pointers. +*/ +static void correctstack (lua_State *L) { CallInfo *ci; UpVal *up; - L->top = (L->top - oldstack) + L->stack; + L->top.p = restorestack(L, L->top.offset); + L->tbclist.p = restorestack(L, L->tbclist.offset); for (up = L->openupval; up != NULL; up = up->u.open.next) - up->v = (up->v - oldstack) + L->stack; + up->v.p = s2v(restorestack(L, up->v.offset)); for (ci = L->ci; ci != NULL; ci = ci->previous) { - ci->top = (ci->top - oldstack) + L->stack; - ci->func = (ci->func - oldstack) + L->stack; + ci->top.p = restorestack(L, ci->top.offset); + ci->func.p = restorestack(L, ci->func.offset); if (isLua(ci)) - ci->u.l.base = (ci->u.l.base - oldstack) + L->stack; + ci->u.l.trap = 1; /* signal to update 'trap' in 'luaV_execute' */ } } @@ -168,308 +198,550 @@ static void correctstack (lua_State *L, TValue *oldstack) { /* some space for error handling */ #define ERRORSTACKSIZE (LUAI_MAXSTACK + 200) - -void luaD_reallocstack (lua_State *L, int newsize) { - TValue *oldstack = L->stack; - int lim = L->stacksize; +/* +** Reallocate the stack to a new size, correcting all pointers into it. +** In ISO C, any pointer use after the pointer has been deallocated is +** undefined behavior. So, before the reallocation, all pointers are +** changed to offsets, and after the reallocation they are changed back +** to pointers. As during the reallocation the pointers are invalid, the +** reallocation cannot run emergency collections. +** +** In case of allocation error, raise an error or return false according +** to 'raiseerror'. +*/ +int luaD_reallocstack (lua_State *L, int newsize, int raiseerror) { + int oldsize = stacksize(L); + int i; + StkId newstack; + int oldgcstop = G(L)->gcstopem; lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE); - lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK); - luaM_reallocvector(L, L->stack, L->stacksize, newsize, TValue); - for (; lim < newsize; lim++) - setnilvalue(L->stack + lim); /* erase new segment */ - L->stacksize = newsize; - L->stack_last = L->stack + newsize - EXTRA_STACK; - correctstack(L, oldstack); + relstack(L); /* change pointers to offsets */ + G(L)->gcstopem = 1; /* stop emergency collection */ + newstack = luaM_reallocvector(L, L->stack.p, oldsize + EXTRA_STACK, + newsize + EXTRA_STACK, StackValue); + G(L)->gcstopem = oldgcstop; /* restore emergency collection */ + if (l_unlikely(newstack == NULL)) { /* reallocation failed? */ + correctstack(L); /* change offsets back to pointers */ + if (raiseerror) + luaM_error(L); + else return 0; /* do not raise an error */ + } + L->stack.p = newstack; + correctstack(L); /* change offsets back to pointers */ + L->stack_last.p = L->stack.p + newsize; + for (i = oldsize + EXTRA_STACK; i < newsize + EXTRA_STACK; i++) + setnilvalue(s2v(newstack + i)); /* erase new segment */ + return 1; } -void luaD_growstack (lua_State *L, int n) { - int size = L->stacksize; - if (size > LUAI_MAXSTACK) /* error after extra size? */ - luaD_throw(L, LUA_ERRERR); - else { - int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK; - int newsize = 2 * size; - if (newsize > LUAI_MAXSTACK) newsize = LUAI_MAXSTACK; - if (newsize < needed) newsize = needed; - if (newsize > LUAI_MAXSTACK) { /* stack overflow? */ - luaD_reallocstack(L, ERRORSTACKSIZE); - luaG_runerror(L, "stack overflow"); - } - else - luaD_reallocstack(L, newsize); +/* +** Try to grow the stack by at least 'n' elements. When 'raiseerror' +** is true, raises any error; otherwise, return 0 in case of errors. +*/ +int luaD_growstack (lua_State *L, int n, int raiseerror) { + int size = stacksize(L); + if (l_unlikely(size > LUAI_MAXSTACK)) { + /* if stack is larger than maximum, thread is already using the + extra space reserved for errors, that is, thread is handling + a stack error; cannot grow further than that. */ + lua_assert(stacksize(L) == ERRORSTACKSIZE); + if (raiseerror) + luaD_throw(L, LUA_ERRERR); /* error inside message handler */ + return 0; /* if not 'raiseerror', just signal it */ } + else if (n < LUAI_MAXSTACK) { /* avoids arithmetic overflows */ + int newsize = 2 * size; /* tentative new size */ + int needed = cast_int(L->top.p - L->stack.p) + n; + if (newsize > LUAI_MAXSTACK) /* cannot cross the limit */ + newsize = LUAI_MAXSTACK; + if (newsize < needed) /* but must respect what was asked for */ + newsize = needed; + if (l_likely(newsize <= LUAI_MAXSTACK)) + return luaD_reallocstack(L, newsize, raiseerror); + } + /* else stack overflow */ + /* add extra size to be able to handle the error message */ + luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror); + if (raiseerror) + luaG_runerror(L, "stack overflow"); + return 0; } +/* +** Compute how much of the stack is being used, by computing the +** maximum top of all call frames in the stack and the current top. +*/ static int stackinuse (lua_State *L) { CallInfo *ci; - StkId lim = L->top; + int res; + StkId lim = L->top.p; for (ci = L->ci; ci != NULL; ci = ci->previous) { - lua_assert(ci->top <= L->stack_last); - if (lim < ci->top) lim = ci->top; + if (lim < ci->top.p) lim = ci->top.p; } - return cast_int(lim - L->stack) + 1; /* part of stack in use */ + lua_assert(lim <= L->stack_last.p + EXTRA_STACK); + res = cast_int(lim - L->stack.p) + 1; /* part of stack in use */ + if (res < LUA_MINSTACK) + res = LUA_MINSTACK; /* ensure a minimum size */ + return res; } +/* +** If stack size is more than 3 times the current use, reduce that size +** to twice the current use. (So, the final stack size is at most 2/3 the +** previous size, and half of its entries are empty.) +** As a particular case, if stack was handling a stack overflow and now +** it is not, 'max' (limited by LUAI_MAXSTACK) will be smaller than +** stacksize (equal to ERRORSTACKSIZE in this case), and so the stack +** will be reduced to a "regular" size. +*/ void luaD_shrinkstack (lua_State *L) { int inuse = stackinuse(L); - int goodsize = inuse + (inuse / 8) + 2*EXTRA_STACK; - if (goodsize > LUAI_MAXSTACK) goodsize = LUAI_MAXSTACK; - if (L->stacksize > LUAI_MAXSTACK) /* was handling stack overflow? */ - luaE_freeCI(L); /* free all CIs (list grew because of an error) */ - else - luaE_shrinkCI(L); /* shrink list */ - if (inuse > LUAI_MAXSTACK || /* still handling stack overflow? */ - goodsize >= L->stacksize) /* would grow instead of shrink? */ - condmovestack(L); /* don't change stack (change only for debugging) */ - else - luaD_reallocstack(L, goodsize); /* shrink it */ + int max = (inuse > LUAI_MAXSTACK / 3) ? LUAI_MAXSTACK : inuse * 3; + /* if thread is currently not handling a stack overflow and its + size is larger than maximum "reasonable" size, shrink it */ + if (inuse <= LUAI_MAXSTACK && stacksize(L) > max) { + int nsize = (inuse > LUAI_MAXSTACK / 2) ? LUAI_MAXSTACK : inuse * 2; + luaD_reallocstack(L, nsize, 0); /* ok if that fails */ + } + else /* don't change stack */ + condmovestack(L,{},{}); /* (change only for debugging) */ + luaE_shrinkCI(L); /* shrink CI list */ +} + + +void luaD_inctop (lua_State *L) { + luaD_checkstack(L, 1); + L->top.p++; } +/* }================================================================== */ + -void luaD_hook (lua_State *L, int event, int line) { +/* +** Call a hook for the given event. Make sure there is a hook to be +** called. (Both 'L->hook' and 'L->hookmask', which trigger this +** function, can be changed asynchronously by signals.) +*/ +void luaD_hook (lua_State *L, int event, int line, + int ftransfer, int ntransfer) { lua_Hook hook = L->hook; - if (hook && L->allowhook) { + if (hook && L->allowhook) { /* make sure there is a hook */ + int mask = CIST_HOOKED; CallInfo *ci = L->ci; - ptrdiff_t top = savestack(L, L->top); - ptrdiff_t ci_top = savestack(L, ci->top); + ptrdiff_t top = savestack(L, L->top.p); /* preserve original 'top' */ + ptrdiff_t ci_top = savestack(L, ci->top.p); /* idem for 'ci->top' */ lua_Debug ar; ar.event = event; ar.currentline = line; ar.i_ci = ci; + if (ntransfer != 0) { + mask |= CIST_TRAN; /* 'ci' has transfer information */ + ci->u2.transferinfo.ftransfer = ftransfer; + ci->u2.transferinfo.ntransfer = ntransfer; + } + if (isLua(ci) && L->top.p < ci->top.p) + L->top.p = ci->top.p; /* protect entire activation register */ luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ - ci->top = L->top + LUA_MINSTACK; - lua_assert(ci->top <= L->stack_last); + if (ci->top.p < L->top.p + LUA_MINSTACK) + ci->top.p = L->top.p + LUA_MINSTACK; L->allowhook = 0; /* cannot call hooks inside a hook */ - ci->callstatus |= CIST_HOOKED; + ci->callstatus |= mask; lua_unlock(L); (*hook)(L, &ar); lua_lock(L); lua_assert(!L->allowhook); L->allowhook = 1; - ci->top = restorestack(L, ci_top); - L->top = restorestack(L, top); - ci->callstatus &= ~CIST_HOOKED; + ci->top.p = restorestack(L, ci_top); + L->top.p = restorestack(L, top); + ci->callstatus &= ~mask; } } -static void callhook (lua_State *L, CallInfo *ci) { - int hook = LUA_HOOKCALL; - ci->u.l.savedpc++; /* hooks assume 'pc' is already incremented */ - if (isLua(ci->previous) && - GET_OPCODE(*(ci->previous->u.l.savedpc - 1)) == OP_TAILCALL) { - ci->callstatus |= CIST_TAIL; - hook = LUA_HOOKTAILCALL; +/* +** Executes a call hook for Lua functions. This function is called +** whenever 'hookmask' is not zero, so it checks whether call hooks are +** active. +*/ +void luaD_hookcall (lua_State *L, CallInfo *ci) { + L->oldpc = 0; /* set 'oldpc' for new function */ + if (L->hookmask & LUA_MASKCALL) { /* is call hook on? */ + int event = (ci->callstatus & CIST_TAIL) ? LUA_HOOKTAILCALL + : LUA_HOOKCALL; + Proto *p = ci_func(ci)->p; + ci->u.l.savedpc++; /* hooks assume 'pc' is already incremented */ + luaD_hook(L, event, -1, 1, p->numparams); + ci->u.l.savedpc--; /* correct 'pc' */ } - luaD_hook(L, hook, -1); - ci->u.l.savedpc--; /* correct 'pc' */ } -static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { - int i; - int nfixargs = p->numparams; - StkId base, fixed; - lua_assert(actual >= nfixargs); - /* move fixed parameters to final position */ - luaD_checkstack(L, p->maxstacksize); /* check again for new 'base' */ - fixed = L->top - actual; /* first fixed argument */ - base = L->top; /* final position of first argument */ - for (i=0; itop++, fixed + i); - setnilvalue(fixed + i); +/* +** Executes a return hook for Lua and C functions and sets/corrects +** 'oldpc'. (Note that this correction is needed by the line hook, so it +** is done even when return hooks are off.) +*/ +static void rethook (lua_State *L, CallInfo *ci, int nres) { + if (L->hookmask & LUA_MASKRET) { /* is return hook on? */ + StkId firstres = L->top.p - nres; /* index of first result */ + int delta = 0; /* correction for vararg functions */ + int ftransfer; + if (isLua(ci)) { + Proto *p = ci_func(ci)->p; + if (p->is_vararg) + delta = ci->u.l.nextraargs + p->numparams + 1; + } + ci->func.p += delta; /* if vararg, back to virtual 'func' */ + ftransfer = cast(unsigned short, firstres - ci->func.p); + luaD_hook(L, LUA_HOOKRET, -1, ftransfer, nres); /* call it */ + ci->func.p -= delta; } - return base; + if (isLua(ci = ci->previous)) + L->oldpc = pcRel(ci->u.l.savedpc, ci_func(ci)->p); /* set 'oldpc' */ } /* -** Check whether __call metafield of 'func' is a function. If so, put -** it in stack below original 'func' so that 'luaD_precall' can call -** it. Raise an error if __call metafield is not a function. +** Check whether 'func' has a '__call' metafield. If so, put it in the +** stack, below original 'func', so that 'luaD_precall' can call it. Raise +** an error if there is no '__call' metafield. */ -static void tryfuncTM (lua_State *L, StkId func) { - const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); +static StkId tryfuncTM (lua_State *L, StkId func) { + const TValue *tm; StkId p; - if (!ttisfunction(tm)) - luaG_typeerror(L, func, "call"); - /* Open a hole inside the stack at 'func' */ - for (p = L->top; p > func; p--) + checkstackGCp(L, 1, func); /* space for metamethod */ + tm = luaT_gettmbyobj(L, s2v(func), TM_CALL); /* (after previous GC) */ + if (l_unlikely(ttisnil(tm))) + luaG_callerror(L, s2v(func)); /* nothing to call */ + for (p = L->top.p; p > func; p--) /* open space for metamethod */ setobjs2s(L, p, p-1); - L->top++; /* slot ensured by caller */ - setobj2s(L, func, tm); /* tag method is the new function to be called */ + L->top.p++; /* stack space pre-allocated by the caller */ + setobj2s(L, func, tm); /* metamethod is the new function to be called */ + return func; +} + + +/* +** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'. +** Handle most typical cases (zero results for commands, one result for +** expressions, multiple results for tail calls/single parameters) +** separated. +*/ +l_sinline void moveresults (lua_State *L, StkId res, int nres, int wanted) { + StkId firstresult; + int i; + switch (wanted) { /* handle typical cases separately */ + case 0: /* no values needed */ + L->top.p = res; + return; + case 1: /* one value needed */ + if (nres == 0) /* no results? */ + setnilvalue(s2v(res)); /* adjust with nil */ + else /* at least one result */ + setobjs2s(L, res, L->top.p - nres); /* move it to proper place */ + L->top.p = res + 1; + return; + case LUA_MULTRET: + wanted = nres; /* we want all results */ + break; + default: /* two/more results and/or to-be-closed variables */ + if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */ + L->ci->callstatus |= CIST_CLSRET; /* in case of yields */ + L->ci->u2.nres = nres; + res = luaF_close(L, res, CLOSEKTOP, 1); + L->ci->callstatus &= ~CIST_CLSRET; + if (L->hookmask) { /* if needed, call hook after '__close's */ + ptrdiff_t savedres = savestack(L, res); + rethook(L, L->ci, nres); + res = restorestack(L, savedres); /* hook can move stack */ + } + wanted = decodeNresults(wanted); + if (wanted == LUA_MULTRET) + wanted = nres; /* we want all results */ + } + break; + } + /* generic case */ + firstresult = L->top.p - nres; /* index of first result */ + if (nres > wanted) /* extra results? */ + nres = wanted; /* don't need them */ + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstresult + i); + for (; i < wanted; i++) /* complete wanted number of results */ + setnilvalue(s2v(res + i)); + L->top.p = res + wanted; /* top points after the last result */ +} + + +/* +** Finishes a function call: calls hook if necessary, moves current +** number of results to proper place, and returns to previous call +** info. If function has to close variables, hook must be called after +** that. +*/ +void luaD_poscall (lua_State *L, CallInfo *ci, int nres) { + int wanted = ci->nresults; + if (l_unlikely(L->hookmask && !hastocloseCfunc(wanted))) + rethook(L, ci, nres); + /* move results to proper place */ + moveresults(L, ci->func.p, nres, wanted); + /* function cannot be in any of these cases when returning */ + lua_assert(!(ci->callstatus & + (CIST_HOOKED | CIST_YPCALL | CIST_FIN | CIST_TRAN | CIST_CLSRET))); + L->ci = ci->previous; /* back to caller (after closing variables) */ } -#define next_ci(L) (L->ci = (L->ci->next ? L->ci->next : luaE_extendCI(L))) +#define next_ci(L) (L->ci->next ? L->ci->next : luaE_extendCI(L)) + + +l_sinline CallInfo *prepCallInfo (lua_State *L, StkId func, int nret, + int mask, StkId top) { + CallInfo *ci = L->ci = next_ci(L); /* new frame */ + ci->func.p = func; + ci->nresults = nret; + ci->callstatus = mask; + ci->top.p = top; + return ci; +} /* -** returns true if function has been executed (C function) +** precall for C functions */ -int luaD_precall (lua_State *L, StkId func, int nresults) { - lua_CFunction f; +l_sinline int precallC (lua_State *L, StkId func, int nresults, + lua_CFunction f) { + int n; /* number of returns */ CallInfo *ci; - int n; /* number of arguments (Lua) or returns (C) */ - ptrdiff_t funcr = savestack(L, func); - switch (ttype(func)) { - case LUA_TLCF: /* light C function */ - f = fvalue(func); - goto Cfunc; - case LUA_TCCL: { /* C closure */ - f = clCvalue(func)->f; - Cfunc: - luaC_checkGC(L); /* stack grow uses memory */ - luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ - ci = next_ci(L); /* now 'enter' new function */ - ci->nresults = nresults; - ci->func = restorestack(L, funcr); - ci->top = L->top + LUA_MINSTACK; - lua_assert(ci->top <= L->stack_last); - ci->callstatus = 0; - if (L->hookmask & LUA_MASKCALL) - luaD_hook(L, LUA_HOOKCALL, -1); - lua_unlock(L); - n = (*f)(L); /* do the actual call */ - lua_lock(L); - api_checknelems(L, n); - luaD_poscall(L, L->top - n, n); - return 1; - } - case LUA_TLCL: { /* Lua function: prepare its call */ - StkId base; - Proto *p = clLvalue(func)->p; - n = cast_int(L->top - func) - 1; /* number of real arguments */ - luaC_checkGC(L); /* stack grow uses memory */ - luaD_checkstack(L, p->maxstacksize); - for (; n < p->numparams; n++) - setnilvalue(L->top++); /* complete missing arguments */ - if (!p->is_vararg) { - func = restorestack(L, funcr); - base = func + 1; - } - else { - base = adjust_varargs(L, p, n); - func = restorestack(L, funcr); /* previous call can change stack */ - } - ci = next_ci(L); /* now 'enter' new function */ - ci->nresults = nresults; - ci->func = func; - ci->u.l.base = base; - ci->top = base + p->maxstacksize; - lua_assert(ci->top <= L->stack_last); + checkstackGCp(L, LUA_MINSTACK, func); /* ensure minimum stack size */ + L->ci = ci = prepCallInfo(L, func, nresults, CIST_C, + L->top.p + LUA_MINSTACK); + lua_assert(ci->top.p <= L->stack_last.p); + if (l_unlikely(L->hookmask & LUA_MASKCALL)) { + int narg = cast_int(L->top.p - func) - 1; + luaD_hook(L, LUA_HOOKCALL, -1, 1, narg); + } + lua_unlock(L); + n = (*f)(L); /* do the actual call */ + lua_lock(L); + api_checknelems(L, n); + luaD_poscall(L, ci, n); + return n; +} + + +/* +** Prepare a function for a tail call, building its call info on top +** of the current call info. 'narg1' is the number of arguments plus 1 +** (so that it includes the function itself). Return the number of +** results, if it was a C function, or -1 for a Lua function. +*/ +int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, + int narg1, int delta) { + retry: + switch (ttypetag(s2v(func))) { + case LUA_VCCL: /* C closure */ + return precallC(L, func, LUA_MULTRET, clCvalue(s2v(func))->f); + case LUA_VLCF: /* light C function */ + return precallC(L, func, LUA_MULTRET, fvalue(s2v(func))); + case LUA_VLCL: { /* Lua function */ + Proto *p = clLvalue(s2v(func))->p; + int fsize = p->maxstacksize; /* frame size */ + int nfixparams = p->numparams; + int i; + checkstackGCp(L, fsize - delta, func); + ci->func.p -= delta; /* restore 'func' (if vararg) */ + for (i = 0; i < narg1; i++) /* move down function and arguments */ + setobjs2s(L, ci->func.p + i, func + i); + func = ci->func.p; /* moved-down function */ + for (; narg1 <= nfixparams; narg1++) + setnilvalue(s2v(func + narg1)); /* complete missing arguments */ + ci->top.p = func + 1 + fsize; /* top for new function */ + lua_assert(ci->top.p <= L->stack_last.p); ci->u.l.savedpc = p->code; /* starting point */ - ci->callstatus = CIST_LUA; - L->top = ci->top; - if (L->hookmask & LUA_MASKCALL) - callhook(L, ci); - return 0; + ci->callstatus |= CIST_TAIL; + L->top.p = func + narg1; /* set top */ + return -1; } default: { /* not a function */ - luaD_checkstack(L, 1); /* ensure space for metamethod */ - func = restorestack(L, funcr); /* previous call may change stack */ - tryfuncTM(L, func); /* try to get '__call' metamethod */ - return luaD_precall(L, func, nresults); /* now it must be a function */ + func = tryfuncTM(L, func); /* try to get '__call' metamethod */ + /* return luaD_pretailcall(L, ci, func, narg1 + 1, delta); */ + narg1++; + goto retry; /* try again */ } } } -int luaD_poscall (lua_State *L, StkId firstResult, int nres) { - StkId res; - int wanted, i; - CallInfo *ci = L->ci; - if (L->hookmask & (LUA_MASKRET | LUA_MASKLINE)) { - if (L->hookmask & LUA_MASKRET) { - ptrdiff_t fr = savestack(L, firstResult); /* hook may change stack */ - luaD_hook(L, LUA_HOOKRET, -1); - firstResult = restorestack(L, fr); +/* +** Prepares the call to a function (C or Lua). For C functions, also do +** the call. The function to be called is at '*func'. The arguments +** are on the stack, right after the function. Returns the CallInfo +** to be executed, if it was a Lua function. Otherwise (a C function) +** returns NULL, with all the results on the stack, starting at the +** original function position. +*/ +CallInfo *luaD_precall (lua_State *L, StkId func, int nresults) { + retry: + switch (ttypetag(s2v(func))) { + case LUA_VCCL: /* C closure */ + precallC(L, func, nresults, clCvalue(s2v(func))->f); + return NULL; + case LUA_VLCF: /* light C function */ + precallC(L, func, nresults, fvalue(s2v(func))); + return NULL; + case LUA_VLCL: { /* Lua function */ + CallInfo *ci; + Proto *p = clLvalue(s2v(func))->p; + int narg = cast_int(L->top.p - func) - 1; /* number of real arguments */ + int nfixparams = p->numparams; + int fsize = p->maxstacksize; /* frame size */ + checkstackGCp(L, fsize, func); + L->ci = ci = prepCallInfo(L, func, nresults, 0, func + 1 + fsize); + ci->u.l.savedpc = p->code; /* starting point */ + for (; narg < nfixparams; narg++) + setnilvalue(s2v(L->top.p++)); /* complete missing arguments */ + lua_assert(ci->top.p <= L->stack_last.p); + return ci; + } + default: { /* not a function */ + func = tryfuncTM(L, func); /* try to get '__call' metamethod */ + /* return luaD_precall(L, func, nresults); */ + goto retry; /* try again with metamethod */ } - L->oldpc = ci->previous->u.l.savedpc; /* 'oldpc' for caller function */ } - res = ci->func; /* res == final position of 1st result */ - wanted = ci->nresults; - L->ci = ci->previous; /* back to caller */ - /* move results to correct place */ - for (i = wanted; i != 0 && nres-- > 0; i--) - setobjs2s(L, res++, firstResult++); - while (i-- > 0) - setnilvalue(res++); - L->top = res; - return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ } /* -** Call a function (C or Lua). The function to be called is at *func. -** The arguments are on the stack, right after the function. -** When returns, all the results are on the stack, starting at the original -** function position. +** Call a function (C or Lua) through C. 'inc' can be 1 (increment +** number of recursive invocations in the C stack) or nyci (the same +** plus increment number of non-yieldable calls). +** This function can be called with some use of EXTRA_STACK, so it should +** check the stack before doing anything else. 'luaD_precall' already +** does that. */ -void luaD_call (lua_State *L, StkId func, int nResults, int allowyield) { - if (++L->nCcalls >= LUAI_MAXCCALLS) { - if (L->nCcalls == LUAI_MAXCCALLS) - luaG_runerror(L, "C stack overflow"); - else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) - luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ +l_sinline void ccall (lua_State *L, StkId func, int nResults, l_uint32 inc) { + CallInfo *ci; + L->nCcalls += inc; + if (l_unlikely(getCcalls(L) >= LUAI_MAXCCALLS)) { + checkstackp(L, 0, func); /* free any use of EXTRA_STACK */ + luaE_checkcstack(L); } - if (!allowyield) L->nny++; - if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ - luaV_execute(L); /* call it */ - if (!allowyield) L->nny--; - L->nCcalls--; + if ((ci = luaD_precall(L, func, nResults)) != NULL) { /* Lua function? */ + ci->callstatus = CIST_FRESH; /* mark that it is a "fresh" execute */ + luaV_execute(L, ci); /* call it */ + } + L->nCcalls -= inc; } /* -** Completes the execution of an interrupted C function, calling its -** continuation function. +** External interface for 'ccall' */ -static void finishCcall (lua_State *L, int status) { - CallInfo *ci = L->ci; - int n; - /* must have a continuation and must be able to call it */ - lua_assert(ci->u.c.k != NULL && L->nny == 0); - /* error status can only happen in a protected call */ - lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD); - if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ - ci->callstatus &= ~CIST_YPCALL; /* finish 'lua_pcall' */ - L->errfunc = ci->u.c.old_errfunc; - } - /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already - handled */ - adjustresults(L, ci->nresults); - /* call continuation function */ - lua_unlock(L); - n = (*ci->u.c.k)(L, status, ci->u.c.ctx); - lua_lock(L); - api_checknelems(L, n); - /* finish 'luaD_precall' */ - luaD_poscall(L, L->top - n, n); +void luaD_call (lua_State *L, StkId func, int nResults) { + ccall(L, func, nResults, 1); +} + + +/* +** Similar to 'luaD_call', but does not allow yields during the call. +*/ +void luaD_callnoyield (lua_State *L, StkId func, int nResults) { + ccall(L, func, nResults, nyci); +} + + +/* +** Finish the job of 'lua_pcallk' after it was interrupted by an yield. +** (The caller, 'finishCcall', does the final call to 'adjustresults'.) +** The main job is to complete the 'luaD_pcall' called by 'lua_pcallk'. +** If a '__close' method yields here, eventually control will be back +** to 'finishCcall' (when that '__close' method finally returns) and +** 'finishpcallk' will run again and close any still pending '__close' +** methods. Similarly, if a '__close' method errs, 'precover' calls +** 'unroll' which calls ''finishCcall' and we are back here again, to +** close any pending '__close' methods. +** Note that, up to the call to 'luaF_close', the corresponding +** 'CallInfo' is not modified, so that this repeated run works like the +** first one (except that it has at least one less '__close' to do). In +** particular, field CIST_RECST preserves the error status across these +** multiple runs, changing only if there is a new error. +*/ +static int finishpcallk (lua_State *L, CallInfo *ci) { + int status = getcistrecst(ci); /* get original status */ + if (l_likely(status == LUA_OK)) /* no error? */ + status = LUA_YIELD; /* was interrupted by an yield */ + else { /* error */ + StkId func = restorestack(L, ci->u2.funcidx); + L->allowhook = getoah(ci->callstatus); /* restore 'allowhook' */ + func = luaF_close(L, func, status, 1); /* can yield or raise an error */ + luaD_seterrorobj(L, status, func); + luaD_shrinkstack(L); /* restore stack size in case of overflow */ + setcistrecst(ci, LUA_OK); /* clear original status */ + } + ci->callstatus &= ~CIST_YPCALL; + L->errfunc = ci->u.c.old_errfunc; + /* if it is here, there were errors or yields; unlike 'lua_pcallk', + do not change status */ + return status; +} + + +/* +** Completes the execution of a C function interrupted by an yield. +** The interruption must have happened while the function was either +** closing its tbc variables in 'moveresults' or executing +** 'lua_callk'/'lua_pcallk'. In the first case, it just redoes +** 'luaD_poscall'. In the second case, the call to 'finishpcallk' +** finishes the interrupted execution of 'lua_pcallk'. After that, it +** calls the continuation of the interrupted function and finally it +** completes the job of the 'luaD_call' that called the function. In +** the call to 'adjustresults', we do not know the number of results +** of the function called by 'lua_callk'/'lua_pcallk', so we are +** conservative and use LUA_MULTRET (always adjust). +*/ +static void finishCcall (lua_State *L, CallInfo *ci) { + int n; /* actual number of results from C function */ + if (ci->callstatus & CIST_CLSRET) { /* was returning? */ + lua_assert(hastocloseCfunc(ci->nresults)); + n = ci->u2.nres; /* just redo 'luaD_poscall' */ + /* don't need to reset CIST_CLSRET, as it will be set again anyway */ + } + else { + int status = LUA_YIELD; /* default if there were no errors */ + /* must have a continuation and must be able to call it */ + lua_assert(ci->u.c.k != NULL && yieldable(L)); + if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */ + status = finishpcallk(L, ci); /* finish it */ + adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */ + lua_unlock(L); + n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */ + lua_lock(L); + api_checknelems(L, n); + } + luaD_poscall(L, ci, n); /* finish 'luaD_call' */ } /* ** Executes "full continuation" (everything in the stack) of a ** previously interrupted coroutine until the stack is empty (or another -** interruption long-jumps out of the loop). If the coroutine is -** recovering from an error, 'ud' points to the error status, which must -** be passed to the first continuation function (otherwise the default -** status is LUA_YIELD). +** interruption long-jumps out of the loop). */ static void unroll (lua_State *L, void *ud) { - if (ud != NULL) /* error status? */ - finishCcall(L, *(int *)ud); /* finish 'lua_pcallk' callee */ - while (L->ci != &L->base_ci) { /* something in the stack */ - if (!isLua(L->ci)) /* C function? */ - finishCcall(L, LUA_YIELD); /* complete its execution */ + CallInfo *ci; + UNUSED(ud); + while ((ci = L->ci) != &L->base_ci) { /* something in the stack */ + if (!isLua(ci)) /* C function? */ + finishCcall(L, ci); /* complete its execution */ else { /* Lua function */ luaV_finishOp(L); /* finish interrupted instruction */ - luaV_execute(L); /* execute down to higher C 'boundary' */ + luaV_execute(L, ci); /* execute down to higher C 'boundary' */ } } } @@ -490,37 +762,16 @@ static CallInfo *findpcall (lua_State *L) { /* -** Recovers from an error in a coroutine. Finds a recover point (if -** there is one) and completes the execution of the interrupted -** 'luaD_pcall'. If there is no recover point, returns zero. -*/ -static int recover (lua_State *L, int status) { - StkId oldtop; - CallInfo *ci = findpcall(L); - if (ci == NULL) return 0; /* no recovery point */ - /* "finish" luaD_pcall */ - oldtop = restorestack(L, ci->extra); - luaF_close(L, oldtop); - seterrorobj(L, status, oldtop); - L->ci = ci; - L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ - L->nny = 0; /* should be zero to be yieldable */ - luaD_shrinkstack(L); - L->errfunc = ci->u.c.old_errfunc; - return 1; /* continue running the coroutine */ -} - - -/* -** signal an error in the call to 'resume', not in the execution of the -** coroutine itself. (Such errors should not be handled by any coroutine -** error handler and should not kill the coroutine.) +** Signal an error in the call to 'lua_resume', not in the execution +** of the coroutine itself. (Such errors should not be handled by any +** coroutine error handler and should not kill the coroutine.) */ -static l_noret resume_error (lua_State *L, const char *msg, StkId firstArg) { - L->top = firstArg; /* remove args from the stack */ - setsvalue2s(L, L->top, luaS_new(L, msg)); /* push error message */ +static int resume_error (lua_State *L, const char *msg, int narg) { + L->top.p -= narg; /* remove args from the stack */ + setsvalue2s(L, L->top.p, luaS_new(L, msg)); /* push error message */ api_incr_top(L); - luaD_throw(L, -1); /* jump back to 'lua_resume' */ + lua_unlock(L); + return LUA_ERRRUN; } @@ -532,99 +783,118 @@ static l_noret resume_error (lua_State *L, const char *msg, StkId firstArg) { ** coroutine. */ static void resume (lua_State *L, void *ud) { - int nCcalls = L->nCcalls; int n = *(cast(int*, ud)); /* number of arguments */ - StkId firstArg = L->top - n; /* first argument */ + StkId firstArg = L->top.p - n; /* first argument */ CallInfo *ci = L->ci; - if (nCcalls >= LUAI_MAXCCALLS) - resume_error(L, "C stack overflow", firstArg); - if (L->status == LUA_OK) { /* may be starting a coroutine */ - if (ci != &L->base_ci) /* not in base level? */ - resume_error(L, "cannot resume non-suspended coroutine", firstArg); - /* coroutine is in base level; start running it */ - if (!luaD_precall(L, firstArg - 1, LUA_MULTRET)) /* Lua function? */ - luaV_execute(L); /* call it */ - } - else if (L->status != LUA_YIELD) - resume_error(L, "cannot resume dead coroutine", firstArg); + if (L->status == LUA_OK) /* starting a coroutine? */ + ccall(L, firstArg - 1, LUA_MULTRET, 0); /* just call its body */ else { /* resuming from previous yield */ + lua_assert(L->status == LUA_YIELD); L->status = LUA_OK; /* mark that it is running (again) */ - ci->func = restorestack(L, ci->extra); - if (isLua(ci)) /* yielded inside a hook? */ - luaV_execute(L); /* just continue running Lua code */ + if (isLua(ci)) { /* yielded inside a hook? */ + /* undo increment made by 'luaG_traceexec': instruction was not + executed yet */ + lua_assert(ci->callstatus & CIST_HOOKYIELD); + ci->u.l.savedpc--; + L->top.p = firstArg; /* discard arguments */ + luaV_execute(L, ci); /* just continue running Lua code */ + } else { /* 'common' yield */ if (ci->u.c.k != NULL) { /* does it have a continuation function? */ lua_unlock(L); n = (*ci->u.c.k)(L, LUA_YIELD, ci->u.c.ctx); /* call continuation */ lua_lock(L); api_checknelems(L, n); - firstArg = L->top - n; /* yield results come from continuation */ } - luaD_poscall(L, firstArg, n); /* finish 'luaD_precall' */ + luaD_poscall(L, ci, n); /* finish 'luaD_call' */ } unroll(L, NULL); /* run continuation */ } - lua_assert(nCcalls == L->nCcalls); } -LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs) { +/* +** Unrolls a coroutine in protected mode while there are recoverable +** errors, that is, errors inside a protected call. (Any error +** interrupts 'unroll', and this loop protects it again so it can +** continue.) Stops with a normal end (status == LUA_OK), an yield +** (status == LUA_YIELD), or an unprotected error ('findpcall' doesn't +** find a recover point). +*/ +static int precover (lua_State *L, int status) { + CallInfo *ci; + while (errorstatus(status) && (ci = findpcall(L)) != NULL) { + L->ci = ci; /* go down to recovery functions */ + setcistrecst(ci, status); /* status to finish 'pcall' */ + status = luaD_rawrunprotected(L, unroll, NULL); + } + return status; +} + + +LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, + int *nresults) { int status; - int oldnny = L->nny; /* save "number of non-yieldable" calls */ lua_lock(L); + if (L->status == LUA_OK) { /* may be starting a coroutine */ + if (L->ci != &L->base_ci) /* not in base level? */ + return resume_error(L, "cannot resume non-suspended coroutine", nargs); + else if (L->top.p - (L->ci->func.p + 1) == nargs) /* no function? */ + return resume_error(L, "cannot resume dead coroutine", nargs); + } + else if (L->status != LUA_YIELD) /* ended with errors? */ + return resume_error(L, "cannot resume dead coroutine", nargs); + L->nCcalls = (from) ? getCcalls(from) : 0; + if (getCcalls(L) >= LUAI_MAXCCALLS) + return resume_error(L, "C stack overflow", nargs); + L->nCcalls++; luai_userstateresume(L, nargs); - L->nCcalls = (from) ? from->nCcalls + 1 : 1; - L->nny = 0; /* allow yields */ api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); status = luaD_rawrunprotected(L, resume, &nargs); - if (status == -1) /* error calling 'lua_resume'? */ - status = LUA_ERRRUN; - else { /* continue running after recoverable errors */ - while (errorstatus(status) && recover(L, status)) { - /* unroll continuation */ - status = luaD_rawrunprotected(L, unroll, &status); - } - if (errorstatus(status)) { /* unrecoverable error? */ - L->status = cast_byte(status); /* mark thread as 'dead' */ - seterrorobj(L, status, L->top); /* push error message */ - L->ci->top = L->top; - } - else lua_assert(status == L->status); /* normal end or yield */ + /* continue running after recoverable errors */ + status = precover(L, status); + if (l_likely(!errorstatus(status))) + lua_assert(status == L->status); /* normal end or yield */ + else { /* unrecoverable error */ + L->status = cast_byte(status); /* mark thread as 'dead' */ + luaD_seterrorobj(L, status, L->top.p); /* push error message */ + L->ci->top.p = L->top.p; } - L->nny = oldnny; /* restore 'nny' */ - L->nCcalls--; - lua_assert(L->nCcalls == ((from) ? from->nCcalls : 0)); + *nresults = (status == LUA_YIELD) ? L->ci->u2.nyield + : cast_int(L->top.p - (L->ci->func.p + 1)); lua_unlock(L); return status; } LUA_API int lua_isyieldable (lua_State *L) { - return (L->nny == 0); + return yieldable(L); } LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, lua_KFunction k) { - CallInfo *ci = L->ci; + CallInfo *ci; luai_userstateyield(L, nresults); lua_lock(L); + ci = L->ci; api_checknelems(L, nresults); - if (L->nny > 0) { + if (l_unlikely(!yieldable(L))) { if (L != G(L)->mainthread) luaG_runerror(L, "attempt to yield across a C-call boundary"); else luaG_runerror(L, "attempt to yield from outside a coroutine"); } L->status = LUA_YIELD; - ci->extra = savestack(L, ci->func); /* save current 'func' */ + ci->u2.nyield = nresults; /* save number of results */ if (isLua(ci)) { /* inside a hook? */ + lua_assert(!isLuacode(ci)); + api_check(L, nresults == 0, "hooks cannot yield values"); api_check(L, k == NULL, "hooks cannot continue after yielding"); } else { if ((ci->u.c.k = k) != NULL) /* is there a continuation? */ ci->u.c.ctx = ctx; /* save context */ - ci->func = L->top - nresults - 1; /* protect stack below results */ luaD_throw(L, LUA_YIELD); } lua_assert(ci->callstatus & CIST_HOOKED); /* must be inside a hook */ @@ -633,23 +903,64 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, } +/* +** Auxiliary structure to call 'luaF_close' in protected mode. +*/ +struct CloseP { + StkId level; + int status; +}; + + +/* +** Auxiliary function to call 'luaF_close' in protected mode. +*/ +static void closepaux (lua_State *L, void *ud) { + struct CloseP *pcl = cast(struct CloseP *, ud); + luaF_close(L, pcl->level, pcl->status, 0); +} + + +/* +** Calls 'luaF_close' in protected mode. Return the original status +** or, in case of errors, the new status. +*/ +int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status) { + CallInfo *old_ci = L->ci; + lu_byte old_allowhooks = L->allowhook; + for (;;) { /* keep closing upvalues until no more errors */ + struct CloseP pcl; + pcl.level = restorestack(L, level); pcl.status = status; + status = luaD_rawrunprotected(L, &closepaux, &pcl); + if (l_likely(status == LUA_OK)) /* no more errors? */ + return pcl.status; + else { /* an error occurred; restore saved state and repeat */ + L->ci = old_ci; + L->allowhook = old_allowhooks; + } + } +} + + +/* +** Call the C function 'func' in protected mode, restoring basic +** thread information ('allowhook', etc.) and in particular +** its stack level in case of errors. +*/ int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) { int status; CallInfo *old_ci = L->ci; lu_byte old_allowhooks = L->allowhook; - unsigned short old_nny = L->nny; ptrdiff_t old_errfunc = L->errfunc; L->errfunc = ef; status = luaD_rawrunprotected(L, func, u); - if (status != LUA_OK) { /* an error occurred? */ - StkId oldtop = restorestack(L, old_top); - luaF_close(L, oldtop); /* close possible pending closures */ - seterrorobj(L, status, oldtop); + if (l_unlikely(status != LUA_OK)) { /* an error occurred? */ L->ci = old_ci; L->allowhook = old_allowhooks; - L->nny = old_nny; - luaD_shrinkstack(L); + status = luaD_closeprotected(L, old_top, status); + luaD_seterrorobj(L, status, restorestack(L, old_top)); + luaD_shrinkstack(L); /* restore stack size in case of overflow */ } L->errfunc = old_errfunc; return status; @@ -684,7 +995,7 @@ static void f_parser (lua_State *L, void *ud) { int c = zgetc(p->z); /* read first character */ if (c == LUA_SIGNATURE[0]) { checkmode(L, p->mode, "binary"); - cl = luaU_undump(L, p->z, &p->buff, p->name); + cl = luaU_undump(L, p->z, p->name); } else { checkmode(L, p->mode, "text"); @@ -699,18 +1010,18 @@ int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, const char *mode) { struct SParser p; int status; - L->nny++; /* cannot yield during parsing */ + incnny(L); /* cannot yield during parsing */ p.z = z; p.name = name; p.mode = mode; p.dyd.actvar.arr = NULL; p.dyd.actvar.size = 0; p.dyd.gt.arr = NULL; p.dyd.gt.size = 0; p.dyd.label.arr = NULL; p.dyd.label.size = 0; luaZ_initbuffer(L, &p.buff); - status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc); + status = luaD_pcall(L, f_parser, &p, savestack(L, L->top.p), L->errfunc); luaZ_freebuffer(L, &p.buff); luaM_freearray(L, p.dyd.actvar.arr, p.dyd.actvar.size); luaM_freearray(L, p.dyd.gt.arr, p.dyd.gt.size); luaM_freearray(L, p.dyd.label.arr, p.dyd.label.size); - L->nny--; + decnny(L); return status; } diff --git a/libs/lua/ldo.h b/libs/lua/ldo.h index edade657..56008ab3 100644 --- a/libs/lua/ldo.h +++ b/libs/lua/ldo.h @@ -1,5 +1,5 @@ /* -** $Id: ldo.h,v 2.22 2015/05/22 17:48:19 roberto Exp $ +** $Id: ldo.h $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ @@ -8,36 +8,77 @@ #define ldo_h +#include "llimits.h" #include "lobject.h" #include "lstate.h" #include "lzio.h" -#define luaD_checkstack(L,n) if (L->stack_last - L->top <= (n)) \ - luaD_growstack(L, n); else condmovestack(L); +/* +** Macro to check stack size and grow stack if needed. Parameters +** 'pre'/'pos' allow the macro to preserve a pointer into the +** stack across reallocations, doing the work only when needed. +** It also allows the running of one GC step when the stack is +** reallocated. +** 'condmovestack' is used in heavy tests to force a stack reallocation +** at every check. +*/ +#define luaD_checkstackaux(L,n,pre,pos) \ + if (l_unlikely(L->stack_last.p - L->top.p <= (n))) \ + { pre; luaD_growstack(L, n, 1); pos; } \ + else { condmovestack(L,pre,pos); } + +/* In general, 'pre'/'pos' are empty (nothing to save) */ +#define luaD_checkstack(L,n) luaD_checkstackaux(L,n,(void)0,(void)0) + + + +#define savestack(L,pt) (cast_charp(pt) - cast_charp(L->stack.p)) +#define restorestack(L,n) cast(StkId, cast_charp(L->stack.p) + (n)) + + +/* macro to check stack size, preserving 'p' */ +#define checkstackp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p), /* save 'p' */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ + +/* macro to check stack size and GC, preserving 'p' */ +#define checkstackGCp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p); /* save 'p' */ \ + luaC_checkGC(L), /* stack grow uses memory */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ -#define incr_top(L) {L->top++; luaD_checkstack(L,0);} -#define savestack(L,p) ((char *)(p) - (char *)L->stack) -#define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) +/* macro to check stack size and GC */ +#define checkstackGC(L,fsize) \ + luaD_checkstackaux(L, (fsize), luaC_checkGC(L), (void)0) /* type of protected functions, to be ran by 'runprotected' */ typedef void (*Pfunc) (lua_State *L, void *ud); +LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, const char *mode); -LUAI_FUNC void luaD_hook (lua_State *L, int event, int line); -LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); -LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults, - int allowyield); +LUAI_FUNC void luaD_hook (lua_State *L, int event, int line, + int fTransfer, int nTransfer); +LUAI_FUNC void luaD_hookcall (lua_State *L, CallInfo *ci); +LUAI_FUNC int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, + int narg1, int delta); +LUAI_FUNC CallInfo *luaD_precall (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); +LUAI_FUNC int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status); LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); -LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult, int nres); -LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); -LUAI_FUNC void luaD_growstack (lua_State *L, int n); +LUAI_FUNC void luaD_poscall (lua_State *L, CallInfo *ci, int nres); +LUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int raiseerror); +LUAI_FUNC int luaD_growstack (lua_State *L, int n, int raiseerror); LUAI_FUNC void luaD_shrinkstack (lua_State *L); +LUAI_FUNC void luaD_inctop (lua_State *L); LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); diff --git a/libs/lua/ldump.c b/libs/lua/ldump.c index 4c04812a..f231691b 100644 --- a/libs/lua/ldump.c +++ b/libs/lua/ldump.c @@ -1,5 +1,5 @@ /* -** $Id: ldump.c,v 2.36 2015/03/30 15:43:51 roberto Exp $ +** $Id: ldump.c $ ** save precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -10,6 +10,7 @@ #include "lprefix.h" +#include #include #include "lua.h" @@ -29,16 +30,16 @@ typedef struct { /* -** All high-level dumps go through DumpVector; you can change it to +** All high-level dumps go through dumpVector; you can change it to ** change the endianness of the result */ -#define DumpVector(v,n,D) DumpBlock(v,(n)*sizeof((v)[0]),D) +#define dumpVector(D,v,n) dumpBlock(D,v,(n)*sizeof((v)[0])) -#define DumpLiteral(s,D) DumpBlock(s, sizeof(s) - sizeof(char), D) +#define dumpLiteral(D, s) dumpBlock(D,s,sizeof(s) - sizeof(char)) -static void DumpBlock (const void *b, size_t size, DumpState *D) { - if (D->status == 0) { +static void dumpBlock (DumpState *D, const void *b, size_t size) { + if (D->status == 0 && size > 0) { lua_unlock(D->L); D->status = (*D->writer)(D->L, b, size, D->data); lua_lock(D->L); @@ -46,153 +47,167 @@ static void DumpBlock (const void *b, size_t size, DumpState *D) { } -#define DumpVar(x,D) DumpVector(&x,1,D) +#define dumpVar(D,x) dumpVector(D,&x,1) -static void DumpByte (int y, DumpState *D) { +static void dumpByte (DumpState *D, int y) { lu_byte x = (lu_byte)y; - DumpVar(x, D); + dumpVar(D, x); } -static void DumpInt (int x, DumpState *D) { - DumpVar(x, D); +/* +** 'dumpSize' buffer size: each byte can store up to 7 bits. (The "+6" +** rounds up the division.) +*/ +#define DIBS ((sizeof(size_t) * CHAR_BIT + 6) / 7) + +static void dumpSize (DumpState *D, size_t x) { + lu_byte buff[DIBS]; + int n = 0; + do { + buff[DIBS - (++n)] = x & 0x7f; /* fill buffer in reverse order */ + x >>= 7; + } while (x != 0); + buff[DIBS - 1] |= 0x80; /* mark last byte */ + dumpVector(D, buff + DIBS - n, n); +} + + +static void dumpInt (DumpState *D, int x) { + dumpSize(D, x); } -static void DumpNumber (lua_Number x, DumpState *D) { - DumpVar(x, D); +static void dumpNumber (DumpState *D, lua_Number x) { + dumpVar(D, x); } -static void DumpInteger (lua_Integer x, DumpState *D) { - DumpVar(x, D); +static void dumpInteger (DumpState *D, lua_Integer x) { + dumpVar(D, x); } -static void DumpString (const TString *s, DumpState *D) { +static void dumpString (DumpState *D, const TString *s) { if (s == NULL) - DumpByte(0, D); + dumpSize(D, 0); else { - size_t size = tsslen(s) + 1; /* include trailing '\0' */ + size_t size = tsslen(s); const char *str = getstr(s); - if (size < 0xFF) - DumpByte(cast_int(size), D); - else { - DumpByte(0xFF, D); - DumpVar(size, D); - } - DumpVector(str, size - 1, D); /* no need to save '\0' */ + dumpSize(D, size + 1); + dumpVector(D, str, size); } } -static void DumpCode (const Proto *f, DumpState *D) { - DumpInt(f->sizecode, D); - DumpVector(f->code, f->sizecode, D); +static void dumpCode (DumpState *D, const Proto *f) { + dumpInt(D, f->sizecode); + dumpVector(D, f->code, f->sizecode); } -static void DumpFunction(const Proto *f, TString *psource, DumpState *D); +static void dumpFunction(DumpState *D, const Proto *f, TString *psource); -static void DumpConstants (const Proto *f, DumpState *D) { +static void dumpConstants (DumpState *D, const Proto *f) { int i; int n = f->sizek; - DumpInt(n, D); + dumpInt(D, n); for (i = 0; i < n; i++) { const TValue *o = &f->k[i]; - DumpByte(ttype(o), D); - switch (ttype(o)) { - case LUA_TNIL: - break; - case LUA_TBOOLEAN: - DumpByte(bvalue(o), D); - break; - case LUA_TNUMFLT: - DumpNumber(fltvalue(o), D); - break; - case LUA_TNUMINT: - DumpInteger(ivalue(o), D); - break; - case LUA_TSHRSTR: - case LUA_TLNGSTR: - DumpString(tsvalue(o), D); - break; - default: - lua_assert(0); + int tt = ttypetag(o); + dumpByte(D, tt); + switch (tt) { + case LUA_VNUMFLT: + dumpNumber(D, fltvalue(o)); + break; + case LUA_VNUMINT: + dumpInteger(D, ivalue(o)); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + dumpString(D, tsvalue(o)); + break; + default: + lua_assert(tt == LUA_VNIL || tt == LUA_VFALSE || tt == LUA_VTRUE); } } } -static void DumpProtos (const Proto *f, DumpState *D) { +static void dumpProtos (DumpState *D, const Proto *f) { int i; int n = f->sizep; - DumpInt(n, D); + dumpInt(D, n); for (i = 0; i < n; i++) - DumpFunction(f->p[i], f->source, D); + dumpFunction(D, f->p[i], f->source); } -static void DumpUpvalues (const Proto *f, DumpState *D) { +static void dumpUpvalues (DumpState *D, const Proto *f) { int i, n = f->sizeupvalues; - DumpInt(n, D); + dumpInt(D, n); for (i = 0; i < n; i++) { - DumpByte(f->upvalues[i].instack, D); - DumpByte(f->upvalues[i].idx, D); + dumpByte(D, f->upvalues[i].instack); + dumpByte(D, f->upvalues[i].idx); + dumpByte(D, f->upvalues[i].kind); } } -static void DumpDebug (const Proto *f, DumpState *D) { +static void dumpDebug (DumpState *D, const Proto *f) { int i, n; n = (D->strip) ? 0 : f->sizelineinfo; - DumpInt(n, D); - DumpVector(f->lineinfo, n, D); + dumpInt(D, n); + dumpVector(D, f->lineinfo, n); + n = (D->strip) ? 0 : f->sizeabslineinfo; + dumpInt(D, n); + for (i = 0; i < n; i++) { + dumpInt(D, f->abslineinfo[i].pc); + dumpInt(D, f->abslineinfo[i].line); + } n = (D->strip) ? 0 : f->sizelocvars; - DumpInt(n, D); + dumpInt(D, n); for (i = 0; i < n; i++) { - DumpString(f->locvars[i].varname, D); - DumpInt(f->locvars[i].startpc, D); - DumpInt(f->locvars[i].endpc, D); + dumpString(D, f->locvars[i].varname); + dumpInt(D, f->locvars[i].startpc); + dumpInt(D, f->locvars[i].endpc); } n = (D->strip) ? 0 : f->sizeupvalues; - DumpInt(n, D); + dumpInt(D, n); for (i = 0; i < n; i++) - DumpString(f->upvalues[i].name, D); + dumpString(D, f->upvalues[i].name); } -static void DumpFunction (const Proto *f, TString *psource, DumpState *D) { +static void dumpFunction (DumpState *D, const Proto *f, TString *psource) { if (D->strip || f->source == psource) - DumpString(NULL, D); /* no debug info or same source as its parent */ + dumpString(D, NULL); /* no debug info or same source as its parent */ else - DumpString(f->source, D); - DumpInt(f->linedefined, D); - DumpInt(f->lastlinedefined, D); - DumpByte(f->numparams, D); - DumpByte(f->is_vararg, D); - DumpByte(f->maxstacksize, D); - DumpCode(f, D); - DumpConstants(f, D); - DumpUpvalues(f, D); - DumpProtos(f, D); - DumpDebug(f, D); + dumpString(D, f->source); + dumpInt(D, f->linedefined); + dumpInt(D, f->lastlinedefined); + dumpByte(D, f->numparams); + dumpByte(D, f->is_vararg); + dumpByte(D, f->maxstacksize); + dumpCode(D, f); + dumpConstants(D, f); + dumpUpvalues(D, f); + dumpProtos(D, f); + dumpDebug(D, f); } -static void DumpHeader (DumpState *D) { - DumpLiteral(LUA_SIGNATURE, D); - DumpByte(LUAC_VERSION, D); - DumpByte(LUAC_FORMAT, D); - DumpLiteral(LUAC_DATA, D); - DumpByte(sizeof(int), D); - DumpByte(sizeof(size_t), D); - DumpByte(sizeof(Instruction), D); - DumpByte(sizeof(lua_Integer), D); - DumpByte(sizeof(lua_Number), D); - DumpInteger(LUAC_INT, D); - DumpNumber(LUAC_NUM, D); +static void dumpHeader (DumpState *D) { + dumpLiteral(D, LUA_SIGNATURE); + dumpByte(D, LUAC_VERSION); + dumpByte(D, LUAC_FORMAT); + dumpLiteral(D, LUAC_DATA); + dumpByte(D, sizeof(Instruction)); + dumpByte(D, sizeof(lua_Integer)); + dumpByte(D, sizeof(lua_Number)); + dumpInteger(D, LUAC_INT); + dumpNumber(D, LUAC_NUM); } @@ -207,9 +222,9 @@ int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data, D.data = data; D.strip = strip; D.status = 0; - DumpHeader(&D); - DumpByte(f->sizeupvalues, &D); - DumpFunction(f, NULL, &D); + dumpHeader(&D); + dumpByte(&D, f->sizeupvalues); + dumpFunction(&D, f, NULL); return D.status; } diff --git a/libs/lua/lfunc.c b/libs/lua/lfunc.c index 67967dab..0945f241 100644 --- a/libs/lua/lfunc.c +++ b/libs/lua/lfunc.c @@ -1,5 +1,5 @@ /* -** $Id: lfunc.c,v 2.45 2014/11/02 19:19:04 roberto Exp $ +** $Id: lfunc.c $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ @@ -14,6 +14,8 @@ #include "lua.h" +#include "ldebug.h" +#include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" @@ -22,92 +24,232 @@ -CClosure *luaF_newCclosure (lua_State *L, int n) { - GCObject *o = luaC_newobj(L, LUA_TCCL, sizeCclosure(n)); +CClosure *luaF_newCclosure (lua_State *L, int nupvals) { + GCObject *o = luaC_newobj(L, LUA_VCCL, sizeCclosure(nupvals)); CClosure *c = gco2ccl(o); - c->nupvalues = cast_byte(n); + c->nupvalues = cast_byte(nupvals); return c; } -LClosure *luaF_newLclosure (lua_State *L, int n) { - GCObject *o = luaC_newobj(L, LUA_TLCL, sizeLclosure(n)); +LClosure *luaF_newLclosure (lua_State *L, int nupvals) { + GCObject *o = luaC_newobj(L, LUA_VLCL, sizeLclosure(nupvals)); LClosure *c = gco2lcl(o); c->p = NULL; - c->nupvalues = cast_byte(n); - while (n--) c->upvals[n] = NULL; + c->nupvalues = cast_byte(nupvals); + while (nupvals--) c->upvals[nupvals] = NULL; return c; } + /* ** fill a closure with new closed upvalues */ void luaF_initupvals (lua_State *L, LClosure *cl) { int i; for (i = 0; i < cl->nupvalues; i++) { - UpVal *uv = luaM_new(L, UpVal); - uv->refcount = 1; - uv->v = &uv->u.value; /* make it closed */ - setnilvalue(uv->v); + GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal)); + UpVal *uv = gco2upv(o); + uv->v.p = &uv->u.value; /* make it closed */ + setnilvalue(uv->v.p); cl->upvals[i] = uv; + luaC_objbarrier(L, cl, uv); } } +/* +** Create a new upvalue at the given level, and link it to the list of +** open upvalues of 'L' after entry 'prev'. +**/ +static UpVal *newupval (lua_State *L, StkId level, UpVal **prev) { + GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal)); + UpVal *uv = gco2upv(o); + UpVal *next = *prev; + uv->v.p = s2v(level); /* current value lives in the stack */ + uv->u.open.next = next; /* link it to list of open upvalues */ + uv->u.open.previous = prev; + if (next) + next->u.open.previous = &uv->u.open.next; + *prev = uv; + if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ + L->twups = G(L)->twups; /* link it to the list */ + G(L)->twups = L; + } + return uv; +} + + +/* +** Find and reuse, or create if it does not exist, an upvalue +** at the given level. +*/ UpVal *luaF_findupval (lua_State *L, StkId level) { UpVal **pp = &L->openupval; UpVal *p; - UpVal *uv; lua_assert(isintwups(L) || L->openupval == NULL); - while (*pp != NULL && (p = *pp)->v >= level) { - lua_assert(upisopen(p)); - if (p->v == level) /* found a corresponding upvalue? */ + while ((p = *pp) != NULL && uplevel(p) >= level) { /* search for it */ + lua_assert(!isdead(G(L), p)); + if (uplevel(p) == level) /* corresponding upvalue? */ return p; /* return it */ pp = &p->u.open.next; } - /* not found: create a new upvalue */ - uv = luaM_new(L, UpVal); - uv->refcount = 0; - uv->u.open.next = *pp; /* link it to list of open upvalues */ - uv->u.open.touched = 1; - *pp = uv; - uv->v = level; /* current value lives in the stack */ - if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ - L->twups = G(L)->twups; /* link it to the list */ - G(L)->twups = L; + /* not found: create a new upvalue after 'pp' */ + return newupval(L, level, pp); +} + + +/* +** Call closing method for object 'obj' with error message 'err'. The +** boolean 'yy' controls whether the call is yieldable. +** (This function assumes EXTRA_STACK.) +*/ +static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) { + StkId top = L->top.p; + const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); + setobj2s(L, top, tm); /* will call metamethod... */ + setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */ + setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */ + L->top.p = top + 3; /* add function and arguments */ + if (yy) + luaD_call(L, top, 0); + else + luaD_callnoyield(L, top, 0); +} + + +/* +** Check whether object at given level has a close metamethod and raise +** an error if not. +*/ +static void checkclosemth (lua_State *L, StkId level) { + const TValue *tm = luaT_gettmbyobj(L, s2v(level), TM_CLOSE); + if (ttisnil(tm)) { /* no metamethod? */ + int idx = cast_int(level - L->ci->func.p); /* variable index */ + const char *vname = luaG_findlocal(L, L->ci, idx, NULL); + if (vname == NULL) vname = "?"; + luaG_runerror(L, "variable '%s' got a non-closable value", vname); } - return uv; } -void luaF_close (lua_State *L, StkId level) { +/* +** Prepare and call a closing method. +** If status is CLOSEKTOP, the call to the closing method will be pushed +** at the top of the stack. Otherwise, values can be pushed right after +** the 'level' of the upvalue being closed, as everything after that +** won't be used again. +*/ +static void prepcallclosemth (lua_State *L, StkId level, int status, int yy) { + TValue *uv = s2v(level); /* value being closed */ + TValue *errobj; + if (status == CLOSEKTOP) + errobj = &G(L)->nilvalue; /* error object is nil */ + else { /* 'luaD_seterrorobj' will set top to level + 2 */ + errobj = s2v(level + 1); /* error object goes after 'uv' */ + luaD_seterrorobj(L, status, level + 1); /* set error object */ + } + callclosemethod(L, uv, errobj, yy); +} + + +/* +** Maximum value for deltas in 'tbclist', dependent on the type +** of delta. (This macro assumes that an 'L' is in scope where it +** is used.) +*/ +#define MAXDELTA \ + ((256ul << ((sizeof(L->stack.p->tbclist.delta) - 1) * 8)) - 1) + + +/* +** Insert a variable in the list of to-be-closed variables. +*/ +void luaF_newtbcupval (lua_State *L, StkId level) { + lua_assert(level > L->tbclist.p); + if (l_isfalse(s2v(level))) + return; /* false doesn't need to be closed */ + checkclosemth(L, level); /* value must have a close method */ + while (cast_uint(level - L->tbclist.p) > MAXDELTA) { + L->tbclist.p += MAXDELTA; /* create a dummy node at maximum delta */ + L->tbclist.p->tbclist.delta = 0; + } + level->tbclist.delta = cast(unsigned short, level - L->tbclist.p); + L->tbclist.p = level; +} + + +void luaF_unlinkupval (UpVal *uv) { + lua_assert(upisopen(uv)); + *uv->u.open.previous = uv->u.open.next; + if (uv->u.open.next) + uv->u.open.next->u.open.previous = uv->u.open.previous; +} + + +/* +** Close all upvalues up to the given stack level. +*/ +void luaF_closeupval (lua_State *L, StkId level) { UpVal *uv; - while (L->openupval != NULL && (uv = L->openupval)->v >= level) { - lua_assert(upisopen(uv)); - L->openupval = uv->u.open.next; /* remove from 'open' list */ - if (uv->refcount == 0) /* no references? */ - luaM_free(L, uv); /* free upvalue */ - else { - setobj(L, &uv->u.value, uv->v); /* move value to upvalue slot */ - uv->v = &uv->u.value; /* now current value lives here */ - luaC_upvalbarrier(L, uv); + StkId upl; /* stack index pointed by 'uv' */ + while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) { + TValue *slot = &uv->u.value; /* new position for value */ + lua_assert(uplevel(uv) < L->top.p); + luaF_unlinkupval(uv); /* remove upvalue from 'openupval' list */ + setobj(L, slot, uv->v.p); /* move value to upvalue slot */ + uv->v.p = slot; /* now current value lives here */ + if (!iswhite(uv)) { /* neither white nor dead? */ + nw2black(uv); /* closed upvalues cannot be gray */ + luaC_barrier(L, uv, slot); } } } +/* +** Remove first element from the tbclist plus its dummy nodes. +*/ +static void poptbclist (lua_State *L) { + StkId tbc = L->tbclist.p; + lua_assert(tbc->tbclist.delta > 0); /* first element cannot be dummy */ + tbc -= tbc->tbclist.delta; + while (tbc > L->stack.p && tbc->tbclist.delta == 0) + tbc -= MAXDELTA; /* remove dummy nodes */ + L->tbclist.p = tbc; +} + + +/* +** Close all upvalues and to-be-closed variables up to the given stack +** level. Return restored 'level'. +*/ +StkId luaF_close (lua_State *L, StkId level, int status, int yy) { + ptrdiff_t levelrel = savestack(L, level); + luaF_closeupval(L, level); /* first, close the upvalues */ + while (L->tbclist.p >= level) { /* traverse tbc's down to that level */ + StkId tbc = L->tbclist.p; /* get variable index */ + poptbclist(L); /* remove it from list */ + prepcallclosemth(L, tbc, status, yy); /* close variable */ + level = restorestack(L, levelrel); + } + return level; +} + + Proto *luaF_newproto (lua_State *L) { - GCObject *o = luaC_newobj(L, LUA_TPROTO, sizeof(Proto)); + GCObject *o = luaC_newobj(L, LUA_VPROTO, sizeof(Proto)); Proto *f = gco2p(o); f->k = NULL; f->sizek = 0; f->p = NULL; f->sizep = 0; f->code = NULL; - f->cache = NULL; f->sizecode = 0; f->lineinfo = NULL; f->sizelineinfo = 0; + f->abslineinfo = NULL; + f->sizeabslineinfo = 0; f->upvalues = NULL; f->sizeupvalues = 0; f->numparams = 0; @@ -127,6 +269,7 @@ void luaF_freeproto (lua_State *L, Proto *f) { luaM_freearray(L, f->p, f->sizep); luaM_freearray(L, f->k, f->sizek); luaM_freearray(L, f->lineinfo, f->sizelineinfo); + luaM_freearray(L, f->abslineinfo, f->sizeabslineinfo); luaM_freearray(L, f->locvars, f->sizelocvars); luaM_freearray(L, f->upvalues, f->sizeupvalues); luaM_free(L, f); diff --git a/libs/lua/lfunc.h b/libs/lua/lfunc.h index 2eeb0d5a..3be265ef 100644 --- a/libs/lua/lfunc.h +++ b/libs/lua/lfunc.h @@ -1,5 +1,5 @@ /* -** $Id: lfunc.h,v 2.15 2015/01/13 15:49:11 roberto Exp $ +** $Id: lfunc.h $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ @@ -11,11 +11,11 @@ #include "lobject.h" -#define sizeCclosure(n) (cast(int, sizeof(CClosure)) + \ - cast(int, sizeof(TValue)*((n)-1))) +#define sizeCclosure(n) (cast_int(offsetof(CClosure, upvalue)) + \ + cast_int(sizeof(TValue)) * (n)) -#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + \ - cast(int, sizeof(TValue *)*((n)-1))) +#define sizeLclosure(n) (cast_int(offsetof(LClosure, upvals)) + \ + cast_int(sizeof(TValue *)) * (n)) /* test whether thread is in 'twups' list */ @@ -29,30 +29,33 @@ #define MAXUPVAL 255 +#define upisopen(up) ((up)->v.p != &(up)->u.value) + + +#define uplevel(up) check_exp(upisopen(up), cast(StkId, (up)->v.p)) + + /* -** Upvalues for Lua closures +** maximum number of misses before giving up the cache of closures +** in prototypes */ -struct UpVal { - TValue *v; /* points to stack or to its own value */ - lu_mem refcount; /* reference counter */ - union { - struct { /* (when open) */ - UpVal *next; /* linked list */ - int touched; /* mark to avoid cycles with dead threads */ - } open; - TValue value; /* the value (when closed) */ - } u; -}; +#define MAXMISS 10 + + -#define upisopen(up) ((up)->v != &(up)->u.value) +/* special status to close upvalues preserving the top of the stack */ +#define CLOSEKTOP (-1) LUAI_FUNC Proto *luaF_newproto (lua_State *L); -LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems); -LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); +LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nupvals); +LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nupvals); LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); -LUAI_FUNC void luaF_close (lua_State *L, StkId level); +LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_closeupval (lua_State *L, StkId level); +LUAI_FUNC StkId luaF_close (lua_State *L, StkId level, int status, int yy); +LUAI_FUNC void luaF_unlinkupval (UpVal *uv); LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, int pc); diff --git a/libs/lua/lgc.c b/libs/lua/lgc.c index 973c269f..5817f9ee 100644 --- a/libs/lua/lgc.c +++ b/libs/lua/lgc.c @@ -1,5 +1,5 @@ /* -** $Id: lgc.c,v 2.205 2015/03/25 13:42:19 roberto Exp $ +** $Id: lgc.c $ ** Garbage Collector ** See Copyright Notice in lua.h */ @@ -9,9 +9,10 @@ #include "lprefix.h" - +#include #include + #include "lua.h" #include "ldebug.h" @@ -27,29 +28,29 @@ /* -** internal state for collector while inside the atomic phase. The -** collector should never be in this state while running regular code. +** Maximum number of elements to sweep in each single step. +** (Large enough to dissipate fixed overheads but small enough +** to allow small steps for the collector.) */ -#define GCSinsideatomic (GCSpause + 1) +#define GCSWEEPMAX 100 /* -** cost of sweeping one element (the size of a small object divided -** by some adjust for the sweep speed) +** Maximum number of finalizers to call in each single step. */ -#define GCSWEEPCOST ((sizeof(TString) + 4) / 4) +#define GCFINMAX 10 -/* maximum number of elements to sweep in each single step */ -#define GCSWEEPMAX (cast_int((GCSTEPSIZE / GCSWEEPCOST) / 4)) -/* cost of calling one finalizer */ -#define GCFINALIZECOST GCSWEEPCOST +/* +** Cost of calling one finalizer. +*/ +#define GCFINALIZECOST 50 /* -** macro to adjust 'stepmul': 'stepmul' is actually used like -** 'stepmul / STEPMULADJ' (value chosen by tests) +** The equivalent, in bytes, of one unit of "work" (visiting a slot, +** sweeping an object, etc.) */ -#define STEPMULADJ 200 +#define WORK2MEM sizeof(TValue) /* @@ -59,30 +60,42 @@ #define PAUSEADJ 100 -/* -** 'makewhite' erases all color bits then sets only the current white -** bit -*/ -#define maskcolors (~(bitmask(BLACKBIT) | WHITEBITS)) +/* mask with all color bits */ +#define maskcolors (bitmask(BLACKBIT) | WHITEBITS) + +/* mask with all GC bits */ +#define maskgcbits (maskcolors | AGEBITS) + + +/* macro to erase all color bits then set only the current white bit */ #define makewhite(g,x) \ - (x->marked = cast_byte((x->marked & maskcolors) | luaC_white(g))) + (x->marked = cast_byte((x->marked & ~maskcolors) | luaC_white(g))) + +/* make an object gray (neither white nor black) */ +#define set2gray(x) resetbits(x->marked, maskcolors) -#define white2gray(x) resetbits(x->marked, WHITEBITS) -#define black2gray(x) resetbit(x->marked, BLACKBIT) + +/* make an object black (coming from any color) */ +#define set2black(x) \ + (x->marked = cast_byte((x->marked & ~WHITEBITS) | bitmask(BLACKBIT))) #define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) -#define checkdeadkey(n) lua_assert(!ttisdeadkey(gkey(n)) || ttisnil(gval(n))) +#define keyiswhite(n) (keyiscollectable(n) && iswhite(gckey(n))) -#define checkconsistency(obj) \ - lua_longassert(!iscollectable(obj) || righttt(obj)) +/* +** Protected access to objects in values +*/ +#define gcvalueN(o) (iscollectable(o) ? gcvalue(o) : NULL) -#define markvalue(g,o) { checkconsistency(o); \ +#define markvalue(g,o) { checkliveness(g->mainthread,o); \ if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); } +#define markkey(g, n) { if keyiswhite(n) reallymarkobject(g,gckey(n)); } + #define markobject(g,t) { if (iswhite(t)) reallymarkobject(g, obj2gco(t)); } /* @@ -92,6 +105,8 @@ #define markobjectN(g,t) { if (t) markobject(g,t); } static void reallymarkobject (global_State *g, GCObject *o); +static lu_mem atomic (lua_State *L); +static void entersweep (lua_State *L); /* @@ -104,23 +119,59 @@ static void reallymarkobject (global_State *g, GCObject *o); /* ** one after last element in a hash array */ -#define gnodelast(h) gnode(h, cast(size_t, sizenode(h))) +#define gnodelast(h) gnode(h, cast_sizet(sizenode(h))) + + +static GCObject **getgclist (GCObject *o) { + switch (o->tt) { + case LUA_VTABLE: return &gco2t(o)->gclist; + case LUA_VLCL: return &gco2lcl(o)->gclist; + case LUA_VCCL: return &gco2ccl(o)->gclist; + case LUA_VTHREAD: return &gco2th(o)->gclist; + case LUA_VPROTO: return &gco2p(o)->gclist; + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + lua_assert(u->nuvalue > 0); + return &u->gclist; + } + default: lua_assert(0); return 0; + } +} + + +/* +** Link a collectable object 'o' with a known type into the list 'p'. +** (Must be a macro to access the 'gclist' field in different types.) +*/ +#define linkgclist(o,p) linkgclist_(obj2gco(o), &(o)->gclist, &(p)) + +static void linkgclist_ (GCObject *o, GCObject **pnext, GCObject **list) { + lua_assert(!isgray(o)); /* cannot be in a gray list */ + *pnext = *list; + *list = o; + set2gray(o); /* now it is */ +} /* -** link collectable object 'o' into list pointed by 'p' +** Link a generic collectable object 'o' into the list 'p'. */ -#define linkgclist(o,p) ((o)->gclist = (p), (p) = obj2gco(o)) +#define linkobjgclist(o,p) linkgclist_(obj2gco(o), getgclist(o), &(p)) + /* -** if key is not marked, mark its entry as dead (therefore removing it -** from the table) +** Clear keys for empty entries in tables. If entry is empty, mark its +** entry as dead. This allows the collection of the key, but keeps its +** entry in the table: its removal could break a chain and could break +** a table traversal. Other places never manipulate dead keys, because +** its associated empty value is enough to signal that the entry is +** logically empty. */ -static void removeentry (Node *n) { - lua_assert(ttisnil(gval(n))); - if (valiswhite(gkey(n))) - setdeadvalue(wgkey(n)); /* unused and unmarked key; remove it */ +static void clearkey (Node *n) { + lua_assert(isempty(gval(n))); + if (keyiscollectable(n)) + setdeadkey(n); /* unused key; remove it */ } @@ -131,30 +182,43 @@ static void removeentry (Node *n) { ** other objects: if really collected, cannot keep them; for objects ** being finalized, keep them in keys, but not in values */ -static int iscleared (global_State *g, const TValue *o) { - if (!iscollectable(o)) return 0; - else if (ttisstring(o)) { - markobject(g, tsvalue(o)); /* strings are 'values', so are never weak */ +static int iscleared (global_State *g, const GCObject *o) { + if (o == NULL) return 0; /* non-collectable value */ + else if (novariant(o->tt) == LUA_TSTRING) { + markobject(g, o); /* strings are 'values', so are never weak */ return 0; } - else return iswhite(gcvalue(o)); + else return iswhite(o); } /* -** barrier that moves collector forward, that is, mark the white object -** being pointed by a black object. (If in sweep phase, clear the black -** object to white [sweep it] to avoid other barrier calls for this -** same object.) +** Barrier that moves collector forward, that is, marks the white object +** 'v' being pointed by the black object 'o'. In the generational +** mode, 'v' must also become old, if 'o' is old; however, it cannot +** be changed directly to OLD, because it may still point to non-old +** objects. So, it is marked as OLD0. In the next cycle it will become +** OLD1, and in the next it will finally become OLD (regular old). By +** then, any object it points to will also be old. If called in the +** incremental sweep phase, it clears the black object to white (sweep +** it) to avoid other barrier calls for this same object. (That cannot +** be done is generational mode, as its sweep does not distinguish +** whites from deads.) */ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { global_State *g = G(L); lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); - if (keepinvariant(g)) /* must keep invariant? */ + if (keepinvariant(g)) { /* must keep invariant? */ reallymarkobject(g, v); /* restore invariant */ + if (isold(o)) { + lua_assert(!isold(v)); /* white object could not be old */ + setage(v, G_OLD0); /* restore generational invariant */ + } + } else { /* sweep phase */ lua_assert(issweepphase(g)); - makewhite(g, o); /* mark main obj. as white to avoid other barriers */ + if (g->gckind == KGC_INC) /* incremental mode? */ + makewhite(g, o); /* mark 'o' as white to avoid other barriers */ } } @@ -163,33 +227,24 @@ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { ** barrier that moves collector backward, that is, mark the black object ** pointing to a white object as gray again. */ -void luaC_barrierback_ (lua_State *L, Table *t) { +void luaC_barrierback_ (lua_State *L, GCObject *o) { global_State *g = G(L); - lua_assert(isblack(t) && !isdead(g, t)); - black2gray(t); /* make table gray (again) */ - linkgclist(t, g->grayagain); -} - - -/* -** barrier for assignments to closed upvalues. Because upvalues are -** shared among closures, it is impossible to know the color of all -** closures pointing to it. So, we assume that the object being assigned -** must be marked. -*/ -void luaC_upvalbarrier_ (lua_State *L, UpVal *uv) { - global_State *g = G(L); - GCObject *o = gcvalue(uv->v); - lua_assert(!upisopen(uv)); /* ensured by macro luaC_upvalbarrier */ - if (keepinvariant(g)) - markobject(g, o); + lua_assert(isblack(o) && !isdead(g, o)); + lua_assert((g->gckind == KGC_GEN) == (isold(o) && getage(o) != G_TOUCHED1)); + if (getage(o) == G_TOUCHED2) /* already in gray list? */ + set2gray(o); /* make it gray to become touched1 */ + else /* link it in 'grayagain' and paint it gray */ + linkobjgclist(o, g->grayagain); + if (isold(o)) /* generational mode? */ + setage(o, G_TOUCHED1); /* touched in current cycle */ } void luaC_fix (lua_State *L, GCObject *o) { global_State *g = G(L); lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */ - white2gray(o); /* they will be gray forever */ + set2gray(o); /* they will be gray forever */ + setage(o, G_OLD); /* and old forever */ g->allgc = o->next; /* remove object from 'allgc' list */ o->next = g->fixedgc; /* link it to 'fixedgc' list */ g->fixedgc = o; @@ -197,12 +252,13 @@ void luaC_fix (lua_State *L, GCObject *o) { /* -** create a new collectable object (with given type and size) and link -** it to 'allgc' list. +** create a new collectable object (with given type, size, and offset) +** and link it to 'allgc' list. */ -GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { +GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz, size_t offset) { global_State *g = G(L); - GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz)); + char *p = cast_charp(luaM_newobject(L, novariant(tt), sz)); + GCObject *o = cast(GCObject *, p + offset); o->marked = luaC_white(g); o->tt = tt; o->next = g->allgc; @@ -210,6 +266,11 @@ GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { return o; } + +GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { + return luaC_newobjdt(L, tt, sz, 0); +} + /* }====================================================== */ @@ -222,55 +283,45 @@ GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { /* -** mark an object. Userdata, strings, and closed upvalues are visited -** and turned black here. Other objects are marked gray and added -** to appropriate list to be visited (and turned black) later. (Open -** upvalues are already linked in 'headuv' list.) +** Mark an object. Userdata with no user values, strings, and closed +** upvalues are visited and turned black here. Open upvalues are +** already indirectly linked through their respective threads in the +** 'twups' list, so they don't go to the gray list; nevertheless, they +** are kept gray to avoid barriers, as their values will be revisited +** by the thread or by 'remarkupvals'. Other objects are added to the +** gray list to be visited (and turned black) later. Both userdata and +** upvalues can call this function recursively, but this recursion goes +** for at most two levels: An upvalue cannot refer to another upvalue +** (only closures can), and a userdata's metatable must be a table. */ static void reallymarkobject (global_State *g, GCObject *o) { - reentry: - white2gray(o); switch (o->tt) { - case LUA_TSHRSTR: { - gray2black(o); - g->GCmemtrav += sizelstring(gco2ts(o)->shrlen); + case LUA_VSHRSTR: + case LUA_VLNGSTR: { + set2black(o); /* nothing to visit */ break; } - case LUA_TLNGSTR: { - gray2black(o); - g->GCmemtrav += sizelstring(gco2ts(o)->u.lnglen); + case LUA_VUPVAL: { + UpVal *uv = gco2upv(o); + if (upisopen(uv)) + set2gray(uv); /* open upvalues are kept gray */ + else + set2black(uv); /* closed upvalues are visited here */ + markvalue(g, uv->v.p); /* mark its content */ break; } - case LUA_TUSERDATA: { - TValue uvalue; - markobjectN(g, gco2u(o)->metatable); /* mark its metatable */ - gray2black(o); - g->GCmemtrav += sizeudata(gco2u(o)); - getuservalue(g->mainthread, gco2u(o), &uvalue); - if (valiswhite(&uvalue)) { /* markvalue(g, &uvalue); */ - o = gcvalue(&uvalue); - goto reentry; + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + if (u->nuvalue == 0) { /* no user values? */ + markobjectN(g, u->metatable); /* mark its metatable */ + set2black(u); /* nothing else to mark */ + break; } - break; - } - case LUA_TLCL: { - linkgclist(gco2lcl(o), g->gray); - break; - } - case LUA_TCCL: { - linkgclist(gco2ccl(o), g->gray); - break; - } - case LUA_TTABLE: { - linkgclist(gco2t(o), g->gray); - break; - } - case LUA_TTHREAD: { - linkgclist(gco2th(o), g->gray); - break; - } - case LUA_TPROTO: { - linkgclist(gco2p(o), g->gray); + /* else... */ + } /* FALLTHROUGH */ + case LUA_VLCL: case LUA_VCCL: case LUA_VTABLE: + case LUA_VTHREAD: case LUA_VPROTO: { + linkobjgclist(o, g->gray); /* to be visited later */ break; } default: lua_assert(0); break; @@ -291,38 +342,58 @@ static void markmt (global_State *g) { /* ** mark all objects in list of being-finalized */ -static void markbeingfnz (global_State *g) { +static lu_mem markbeingfnz (global_State *g) { GCObject *o; - for (o = g->tobefnz; o != NULL; o = o->next) + lu_mem count = 0; + for (o = g->tobefnz; o != NULL; o = o->next) { + count++; markobject(g, o); + } + return count; } /* -** Mark all values stored in marked open upvalues from non-marked threads. -** (Values from marked threads were already marked when traversing the -** thread.) Remove from the list threads that no longer have upvalues and -** not-marked threads. +** For each non-marked thread, simulates a barrier between each open +** upvalue and its value. (If the thread is collected, the value will be +** assigned to the upvalue, but then it can be too late for the barrier +** to act. The "barrier" does not need to check colors: A non-marked +** thread must be young; upvalues cannot be older than their threads; so +** any visited upvalue must be young too.) Also removes the thread from +** the list, as it was already visited. Removes also threads with no +** upvalues, as they have nothing to be checked. (If the thread gets an +** upvalue later, it will be linked in the list again.) */ -static void remarkupvals (global_State *g) { +static int remarkupvals (global_State *g) { lua_State *thread; lua_State **p = &g->twups; + int work = 0; /* estimate of how much work was done here */ while ((thread = *p) != NULL) { - lua_assert(!isblack(thread)); /* threads are never black */ - if (isgray(thread) && thread->openupval != NULL) + work++; + if (!iswhite(thread) && thread->openupval != NULL) p = &thread->twups; /* keep marked thread with upvalues in the list */ else { /* thread is not marked or without upvalues */ UpVal *uv; + lua_assert(!isold(thread) || thread->openupval == NULL); *p = thread->twups; /* remove thread from the list */ thread->twups = thread; /* mark that it is out of list */ for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) { - if (uv->u.open.touched) { - markvalue(g, uv->v); /* remark upvalue's value */ - uv->u.open.touched = 0; + lua_assert(getage(uv) <= getage(thread)); + work++; + if (!iswhite(uv)) { /* upvalue already visited? */ + lua_assert(upisopen(uv) && isgray(uv)); + markvalue(g, uv->v.p); /* mark its value */ } } } } + return work; +} + + +static void cleargraylists (global_State *g) { + g->gray = g->grayagain = NULL; + g->weak = g->allweak = g->ephemeron = NULL; } @@ -330,8 +401,7 @@ static void remarkupvals (global_State *g) { ** mark root set and reset all gray lists, to start a new collection */ static void restartcollection (global_State *g) { - g->gray = g->grayagain = NULL; - g->weak = g->allweak = g->ephemeron = NULL; + cleargraylists(g); markobject(g, g->mainthread); markvalue(g, &g->l_registry); markmt(g); @@ -347,6 +417,26 @@ static void restartcollection (global_State *g) { ** ======================================================= */ + +/* +** Check whether object 'o' should be kept in the 'grayagain' list for +** post-processing by 'correctgraylist'. (It could put all old objects +** in the list and leave all the work to 'correctgraylist', but it is +** more efficient to avoid adding elements that will be removed.) Only +** TOUCHED1 objects need to be in the list. TOUCHED2 doesn't need to go +** back to a gray list, but then it must become OLD. (That is what +** 'correctgraylist' does when it finds a TOUCHED2 object.) +*/ +static void genlink (global_State *g, GCObject *o) { + lua_assert(isblack(o)); + if (getage(o) == G_TOUCHED1) { /* touched in this cycle? */ + linkobjgclist(o, g->grayagain); /* link it back in 'grayagain' */ + } /* everything else do not need to be linked back */ + else if (getage(o) == G_TOUCHED2) + changeage(o, G_TOUCHED2, G_OLD); /* advance age */ +} + + /* ** Traverse a table with weak values and link it to proper list. During ** propagate phase, keep it in 'grayagain' list, to be revisited in the @@ -357,22 +447,21 @@ static void traverseweakvalue (global_State *g, Table *h) { Node *n, *limit = gnodelast(h); /* if there is array part, assume it may have white values (it is not worth traversing it now just to check) */ - int hasclears = (h->sizearray > 0); + int hasclears = (h->alimit > 0); for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ - checkdeadkey(n); - if (ttisnil(gval(n))) /* entry is empty? */ - removeentry(n); /* remove it */ + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ else { - lua_assert(!ttisnil(gkey(n))); - markvalue(g, gkey(n)); /* mark key */ - if (!hasclears && iscleared(g, gval(n))) /* is there a white value? */ + lua_assert(!keyisnil(n)); + markkey(g, n); + if (!hasclears && iscleared(g, gcvalueN(gval(n)))) /* a white value? */ hasclears = 1; /* table will have to be cleared */ } } - if (g->gcstate == GCSpropagate) - linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ - else if (hasclears) + if (g->gcstate == GCSatomic && hasclears) linkgclist(h, g->weak); /* has to be cleared later */ + else + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ } @@ -384,27 +473,31 @@ static void traverseweakvalue (global_State *g, Table *h) { ** the atomic phase, if table has any white->white entry, it has to ** be revisited during ephemeron convergence (as that key may turn ** black). Otherwise, if it has any white key, table has to be cleared -** (in the atomic phase). +** (in the atomic phase). In generational mode, some tables +** must be kept in some gray list for post-processing; this is done +** by 'genlink'. */ -static int traverseephemeron (global_State *g, Table *h) { +static int traverseephemeron (global_State *g, Table *h, int inv) { int marked = 0; /* true if an object is marked in this traversal */ int hasclears = 0; /* true if table has white keys */ int hasww = 0; /* true if table has entry "white-key -> white-value" */ - Node *n, *limit = gnodelast(h); unsigned int i; + unsigned int asize = luaH_realasize(h); + unsigned int nsize = sizenode(h); /* traverse array part */ - for (i = 0; i < h->sizearray; i++) { + for (i = 0; i < asize; i++) { if (valiswhite(&h->array[i])) { marked = 1; reallymarkobject(g, gcvalue(&h->array[i])); } } - /* traverse hash part */ - for (n = gnode(h, 0); n < limit; n++) { - checkdeadkey(n); - if (ttisnil(gval(n))) /* entry is empty? */ - removeentry(n); /* remove it */ - else if (iscleared(g, gkey(n))) { /* key is not marked (yet)? */ + /* traverse hash part; if 'inv', traverse descending + (see 'convergeephemerons') */ + for (i = 0; i < nsize; i++) { + Node *n = inv ? gnode(h, nsize - 1 - i) : gnode(h, i); + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ + else if (iscleared(g, gckeyN(n))) { /* key is not marked (yet)? */ hasclears = 1; /* table must be cleared */ if (valiswhite(gval(n))) /* value not marked yet? */ hasww = 1; /* white-white entry */ @@ -421,6 +514,8 @@ static int traverseephemeron (global_State *g, Table *h) { linkgclist(h, g->ephemeron); /* have to propagate again */ else if (hasclears) /* table has white keys? */ linkgclist(h, g->allweak); /* may have to clean white keys */ + else + genlink(g, obj2gco(h)); /* check whether collector still needs to see it */ return marked; } @@ -428,41 +523,52 @@ static int traverseephemeron (global_State *g, Table *h) { static void traversestrongtable (global_State *g, Table *h) { Node *n, *limit = gnodelast(h); unsigned int i; - for (i = 0; i < h->sizearray; i++) /* traverse array part */ + unsigned int asize = luaH_realasize(h); + for (i = 0; i < asize; i++) /* traverse array part */ markvalue(g, &h->array[i]); for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ - checkdeadkey(n); - if (ttisnil(gval(n))) /* entry is empty? */ - removeentry(n); /* remove it */ + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ else { - lua_assert(!ttisnil(gkey(n))); - markvalue(g, gkey(n)); /* mark key */ - markvalue(g, gval(n)); /* mark value */ + lua_assert(!keyisnil(n)); + markkey(g, n); + markvalue(g, gval(n)); } } + genlink(g, obj2gco(h)); } static lu_mem traversetable (global_State *g, Table *h) { const char *weakkey, *weakvalue; const TValue *mode = gfasttm(g, h->metatable, TM_MODE); + TString *smode; markobjectN(g, h->metatable); - if (mode && ttisstring(mode) && /* is there a weak mode? */ - ((weakkey = strchr(svalue(mode), 'k')), - (weakvalue = strchr(svalue(mode), 'v')), + if (mode && ttisshrstring(mode) && /* is there a weak mode? */ + (cast_void(smode = tsvalue(mode)), + cast_void(weakkey = strchr(getshrstr(smode), 'k')), + cast_void(weakvalue = strchr(getshrstr(smode), 'v')), (weakkey || weakvalue))) { /* is really weak? */ - black2gray(h); /* keep table gray */ if (!weakkey) /* strong keys? */ traverseweakvalue(g, h); else if (!weakvalue) /* strong values? */ - traverseephemeron(g, h); + traverseephemeron(g, h, 0); else /* all weak */ linkgclist(h, g->allweak); /* nothing to traverse now */ } else /* not weak */ traversestrongtable(g, h); - return sizeof(Table) + sizeof(TValue) * h->sizearray + - sizeof(Node) * cast(size_t, sizenode(h)); + return 1 + h->alimit + 2 * allocsizenode(h); +} + + +static int traverseudata (global_State *g, Udata *u) { + int i; + markobjectN(g, u->metatable); /* mark its metatable */ + for (i = 0; i < u->nuvalue; i++) + markvalue(g, &u->uv[i].uv); + genlink(g, obj2gco(u)); + return 1 + u->nuvalue; } @@ -473,8 +579,6 @@ static lu_mem traversetable (global_State *g, Table *h) { */ static int traverseproto (global_State *g, Proto *f) { int i; - if (f->cache && iswhite(f->cache)) - f->cache = NULL; /* allow cache to be collected */ markobjectN(g, f->source); for (i = 0; i < f->sizek; i++) /* mark literals */ markvalue(g, &f->k[i]); @@ -484,136 +588,125 @@ static int traverseproto (global_State *g, Proto *f) { markobjectN(g, f->p[i]); for (i = 0; i < f->sizelocvars; i++) /* mark local-variable names */ markobjectN(g, f->locvars[i].varname); - return sizeof(Proto) + sizeof(Instruction) * f->sizecode + - sizeof(Proto *) * f->sizep + - sizeof(TValue) * f->sizek + - sizeof(int) * f->sizelineinfo + - sizeof(LocVar) * f->sizelocvars + - sizeof(Upvaldesc) * f->sizeupvalues; + return 1 + f->sizek + f->sizeupvalues + f->sizep + f->sizelocvars; } -static lu_mem traverseCclosure (global_State *g, CClosure *cl) { +static int traverseCclosure (global_State *g, CClosure *cl) { int i; for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ markvalue(g, &cl->upvalue[i]); - return sizeCclosure(cl->nupvalues); + return 1 + cl->nupvalues; } /* -** open upvalues point to values in a thread, so those values should -** be marked when the thread is traversed except in the atomic phase -** (because then the value cannot be changed by the thread and the -** thread may not be traversed again) +** Traverse a Lua closure, marking its prototype and its upvalues. +** (Both can be NULL while closure is being created.) */ -static lu_mem traverseLclosure (global_State *g, LClosure *cl) { +static int traverseLclosure (global_State *g, LClosure *cl) { int i; markobjectN(g, cl->p); /* mark its prototype */ - for (i = 0; i < cl->nupvalues; i++) { /* mark its upvalues */ + for (i = 0; i < cl->nupvalues; i++) { /* visit its upvalues */ UpVal *uv = cl->upvals[i]; - if (uv != NULL) { - if (upisopen(uv) && g->gcstate != GCSinsideatomic) - uv->u.open.touched = 1; /* can be marked in 'remarkupvals' */ - else - markvalue(g, uv->v); - } + markobjectN(g, uv); /* mark upvalue */ } - return sizeLclosure(cl->nupvalues); + return 1 + cl->nupvalues; } -static lu_mem traversethread (global_State *g, lua_State *th) { - StkId o = th->stack; +/* +** Traverse a thread, marking the elements in the stack up to its top +** and cleaning the rest of the stack in the final traversal. That +** ensures that the entire stack have valid (non-dead) objects. +** Threads have no barriers. In gen. mode, old threads must be visited +** at every cycle, because they might point to young objects. In inc. +** mode, the thread can still be modified before the end of the cycle, +** and therefore it must be visited again in the atomic phase. To ensure +** these visits, threads must return to a gray list if they are not new +** (which can only happen in generational mode) or if the traverse is in +** the propagate phase (which can only happen in incremental mode). +*/ +static int traversethread (global_State *g, lua_State *th) { + UpVal *uv; + StkId o = th->stack.p; + if (isold(th) || g->gcstate == GCSpropagate) + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ if (o == NULL) return 1; /* stack not completely built yet */ - lua_assert(g->gcstate == GCSinsideatomic || + lua_assert(g->gcstate == GCSatomic || th->openupval == NULL || isintwups(th)); - for (; o < th->top; o++) /* mark live elements in the stack */ - markvalue(g, o); - if (g->gcstate == GCSinsideatomic) { /* final traversal? */ - StkId lim = th->stack + th->stacksize; /* real end of stack */ - for (; o < lim; o++) /* clear not-marked stack slice */ - setnilvalue(o); - /* 'remarkupvals' may have removed thread from 'twups' list */ + for (; o < th->top.p; o++) /* mark live elements in the stack */ + markvalue(g, s2v(o)); + for (uv = th->openupval; uv != NULL; uv = uv->u.open.next) + markobject(g, uv); /* open upvalues cannot be collected */ + if (g->gcstate == GCSatomic) { /* final traversal? */ + if (!g->gcemergency) + luaD_shrinkstack(th); /* do not change stack in emergency cycle */ + for (o = th->top.p; o < th->stack_last.p + EXTRA_STACK; o++) + setnilvalue(s2v(o)); /* clear dead stack slice */ + /* 'remarkupvals' may have removed thread from 'twups' list */ if (!isintwups(th) && th->openupval != NULL) { th->twups = g->twups; /* link it back to the list */ g->twups = th; } } - else if (g->gckind != KGC_EMERGENCY) - luaD_shrinkstack(th); /* do not change stack in emergency cycle */ - return (sizeof(lua_State) + sizeof(TValue) * th->stacksize); + return 1 + stacksize(th); } /* -** traverse one gray object, turning it to black (except for threads, -** which are always gray). +** traverse one gray object, turning it to black. */ -static void propagatemark (global_State *g) { - lu_mem size; +static lu_mem propagatemark (global_State *g) { GCObject *o = g->gray; - lua_assert(isgray(o)); - gray2black(o); + nw2black(o); + g->gray = *getgclist(o); /* remove from 'gray' list */ switch (o->tt) { - case LUA_TTABLE: { - Table *h = gco2t(o); - g->gray = h->gclist; /* remove from 'gray' list */ - size = traversetable(g, h); - break; - } - case LUA_TLCL: { - LClosure *cl = gco2lcl(o); - g->gray = cl->gclist; /* remove from 'gray' list */ - size = traverseLclosure(g, cl); - break; - } - case LUA_TCCL: { - CClosure *cl = gco2ccl(o); - g->gray = cl->gclist; /* remove from 'gray' list */ - size = traverseCclosure(g, cl); - break; - } - case LUA_TTHREAD: { - lua_State *th = gco2th(o); - g->gray = th->gclist; /* remove from 'gray' list */ - linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ - black2gray(o); - size = traversethread(g, th); - break; - } - case LUA_TPROTO: { - Proto *p = gco2p(o); - g->gray = p->gclist; /* remove from 'gray' list */ - size = traverseproto(g, p); - break; - } - default: lua_assert(0); return; + case LUA_VTABLE: return traversetable(g, gco2t(o)); + case LUA_VUSERDATA: return traverseudata(g, gco2u(o)); + case LUA_VLCL: return traverseLclosure(g, gco2lcl(o)); + case LUA_VCCL: return traverseCclosure(g, gco2ccl(o)); + case LUA_VPROTO: return traverseproto(g, gco2p(o)); + case LUA_VTHREAD: return traversethread(g, gco2th(o)); + default: lua_assert(0); return 0; } - g->GCmemtrav += size; } -static void propagateall (global_State *g) { - while (g->gray) propagatemark(g); +static lu_mem propagateall (global_State *g) { + lu_mem tot = 0; + while (g->gray) + tot += propagatemark(g); + return tot; } +/* +** Traverse all ephemeron tables propagating marks from keys to values. +** Repeat until it converges, that is, nothing new is marked. 'dir' +** inverts the direction of the traversals, trying to speed up +** convergence on chains in the same table. +** +*/ static void convergeephemerons (global_State *g) { int changed; + int dir = 0; do { GCObject *w; GCObject *next = g->ephemeron; /* get ephemeron list */ g->ephemeron = NULL; /* tables may return to this list when traversed */ changed = 0; - while ((w = next) != NULL) { - next = gco2t(w)->gclist; - if (traverseephemeron(g, gco2t(w))) { /* traverse marked some value? */ + while ((w = next) != NULL) { /* for each ephemeron table */ + Table *h = gco2t(w); + next = h->gclist; /* list is rebuilt during loop */ + nw2black(h); /* out of the list (for now) */ + if (traverseephemeron(g, h, dir)) { /* marked some value? */ propagateall(g); /* propagate changes */ changed = 1; /* will have to revisit all ephemeron tables */ } } - } while (changed); + dir = !dir; /* invert direction next time */ + } while (changed); /* repeat until no more changes */ } /* }====================================================== */ @@ -627,18 +720,18 @@ static void convergeephemerons (global_State *g) { /* -** clear entries with unmarked keys from all weaktables in list 'l' up -** to element 'f' +** clear entries with unmarked keys from all weaktables in list 'l' */ -static void clearkeys (global_State *g, GCObject *l, GCObject *f) { - for (; l != f; l = gco2t(l)->gclist) { +static void clearbykeys (global_State *g, GCObject *l) { + for (; l; l = gco2t(l)->gclist) { Table *h = gco2t(l); - Node *n, *limit = gnodelast(h); + Node *limit = gnodelast(h); + Node *n; for (n = gnode(h, 0); n < limit; n++) { - if (!ttisnil(gval(n)) && (iscleared(g, gkey(n)))) { - setnilvalue(gval(n)); /* remove value ... */ - removeentry(n); /* and remove entry from table */ - } + if (iscleared(g, gckeyN(n))) /* unmarked key? */ + setempty(gval(n)); /* remove entry */ + if (isempty(gval(n))) /* is entry empty? */ + clearkey(n); /* clear its key */ } } } @@ -648,65 +741,72 @@ static void clearkeys (global_State *g, GCObject *l, GCObject *f) { ** clear entries with unmarked values from all weaktables in list 'l' up ** to element 'f' */ -static void clearvalues (global_State *g, GCObject *l, GCObject *f) { +static void clearbyvalues (global_State *g, GCObject *l, GCObject *f) { for (; l != f; l = gco2t(l)->gclist) { Table *h = gco2t(l); Node *n, *limit = gnodelast(h); unsigned int i; - for (i = 0; i < h->sizearray; i++) { + unsigned int asize = luaH_realasize(h); + for (i = 0; i < asize; i++) { TValue *o = &h->array[i]; - if (iscleared(g, o)) /* value was collected? */ - setnilvalue(o); /* remove value */ + if (iscleared(g, gcvalueN(o))) /* value was collected? */ + setempty(o); /* remove entry */ } for (n = gnode(h, 0); n < limit; n++) { - if (!ttisnil(gval(n)) && iscleared(g, gval(n))) { - setnilvalue(gval(n)); /* remove value ... */ - removeentry(n); /* and remove entry from table */ - } + if (iscleared(g, gcvalueN(gval(n)))) /* unmarked value? */ + setempty(gval(n)); /* remove entry */ + if (isempty(gval(n))) /* is entry empty? */ + clearkey(n); /* clear its key */ } } } -void luaC_upvdeccount (lua_State *L, UpVal *uv) { - lua_assert(uv->refcount > 0); - uv->refcount--; - if (uv->refcount == 0 && !upisopen(uv)) - luaM_free(L, uv); -} - - -static void freeLclosure (lua_State *L, LClosure *cl) { - int i; - for (i = 0; i < cl->nupvalues; i++) { - UpVal *uv = cl->upvals[i]; - if (uv) - luaC_upvdeccount(L, uv); - } - luaM_freemem(L, cl, sizeLclosure(cl->nupvalues)); +static void freeupval (lua_State *L, UpVal *uv) { + if (upisopen(uv)) + luaF_unlinkupval(uv); + luaM_free(L, uv); } static void freeobj (lua_State *L, GCObject *o) { switch (o->tt) { - case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break; - case LUA_TLCL: { - freeLclosure(L, gco2lcl(o)); + case LUA_VPROTO: + luaF_freeproto(L, gco2p(o)); + break; + case LUA_VUPVAL: + freeupval(L, gco2upv(o)); + break; + case LUA_VLCL: { + LClosure *cl = gco2lcl(o); + luaM_freemem(L, cl, sizeLclosure(cl->nupvalues)); + break; + } + case LUA_VCCL: { + CClosure *cl = gco2ccl(o); + luaM_freemem(L, cl, sizeCclosure(cl->nupvalues)); break; } - case LUA_TCCL: { - luaM_freemem(L, o, sizeCclosure(gco2ccl(o)->nupvalues)); + case LUA_VTABLE: + luaH_free(L, gco2t(o)); + break; + case LUA_VTHREAD: + luaE_freethread(L, gco2th(o)); + break; + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + luaM_freemem(L, o, sizeudata(u->nuvalue, u->len)); break; } - case LUA_TTABLE: luaH_free(L, gco2t(o)); break; - case LUA_TTHREAD: luaE_freethread(L, gco2th(o)); break; - case LUA_TUSERDATA: luaM_freemem(L, o, sizeudata(gco2u(o))); break; - case LUA_TSHRSTR: - luaS_remove(L, gco2ts(o)); /* remove it from hash table */ - luaM_freemem(L, o, sizelstring(gco2ts(o)->shrlen)); + case LUA_VSHRSTR: { + TString *ts = gco2ts(o); + luaS_remove(L, ts); /* remove it from hash table */ + luaM_freemem(L, ts, sizelstring(ts->shrlen)); break; - case LUA_TLNGSTR: { - luaM_freemem(L, o, sizelstring(gco2ts(o)->u.lnglen)); + } + case LUA_VLNGSTR: { + TString *ts = gco2ts(o); + luaM_freemem(L, ts, sizelstring(ts->u.lnglen)); break; } default: lua_assert(0); @@ -714,22 +814,20 @@ static void freeobj (lua_State *L, GCObject *o) { } -#define sweepwholelist(L,p) sweeplist(L,p,MAX_LUMEM) -static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count); - - /* -** sweep at most 'count' elements from a list of GCObjects erasing dead +** sweep at most 'countin' elements from a list of GCObjects erasing dead ** objects, where a dead object is one marked with the old (non current) ** white; change all non-dead objects back to white, preparing for next ** collection cycle. Return where to continue the traversal or NULL if -** list is finished. +** list is finished. ('*countout' gets the number of elements traversed.) */ -static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { +static GCObject **sweeplist (lua_State *L, GCObject **p, int countin, + int *countout) { global_State *g = G(L); int ow = otherwhite(g); + int i; int white = luaC_white(g); /* current white */ - while (*p != NULL && count-- > 0) { + for (i = 0; *p != NULL && i < countin; i++) { GCObject *curr = *p; int marked = curr->marked; if (isdeadm(ow, marked)) { /* is 'curr' dead? */ @@ -737,10 +835,12 @@ static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { freeobj(L, curr); /* erase 'curr' */ } else { /* change mark to 'white' */ - curr->marked = cast_byte((marked & maskcolors) | white); + curr->marked = cast_byte((marked & ~maskgcbits) | white); p = &curr->next; /* go to next element */ } } + if (countout) + *countout = i; /* number of elements traversed */ return (*p == NULL) ? NULL : p; } @@ -748,14 +848,11 @@ static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { /* ** sweep a list until a live object (or end of list) */ -static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { +static GCObject **sweeptolive (lua_State *L, GCObject **p) { GCObject **old = p; - int i = 0; do { - i++; - p = sweeplist(L, p, 1); + p = sweeplist(L, p, 1, NULL); } while (p == old); - if (n) *n += i; return p; } @@ -769,19 +866,23 @@ static GCObject **sweeptolive (lua_State *L, GCObject **p, int *n) { */ /* -** If possible, free concatenation buffer and shrink string table +** If possible, shrink string table. */ static void checkSizes (lua_State *L, global_State *g) { - if (g->gckind != KGC_EMERGENCY) { - l_mem olddebt = g->GCdebt; - luaZ_freebuffer(L, &g->buff); /* free concatenation buffer */ - if (g->strt.nuse < g->strt.size / 4) /* string table too big? */ - luaS_resize(L, g->strt.size / 2); /* shrink it a little */ - g->GCestimate += g->GCdebt - olddebt; /* update estimate */ + if (!g->gcemergency) { + if (g->strt.nuse < g->strt.size / 4) { /* string table too big? */ + l_mem olddebt = g->GCdebt; + luaS_resize(L, g->strt.size / 2); + g->GCestimate += g->GCdebt - olddebt; /* correct estimate */ + } } } +/* +** Get the next udata to be finalized from the 'tobefnz' list, and +** link it back into the 'allgc' list. +*/ static GCObject *udata2finalize (global_State *g) { GCObject *o = g->tobefnz; /* get first element */ lua_assert(tofinalize(o)); @@ -791,59 +892,54 @@ static GCObject *udata2finalize (global_State *g) { resetbit(o->marked, FINALIZEDBIT); /* object is "normal" again */ if (issweepphase(g)) makewhite(g, o); /* "sweep" object */ + else if (getage(o) == G_OLD1) + g->firstold1 = o; /* it is the first OLD1 object in the list */ return o; } static void dothecall (lua_State *L, void *ud) { UNUSED(ud); - luaD_call(L, L->top - 2, 0, 0); + luaD_callnoyield(L, L->top.p - 2, 0); } -static void GCTM (lua_State *L, int propagateerrors) { +static void GCTM (lua_State *L) { global_State *g = G(L); const TValue *tm; TValue v; + lua_assert(!g->gcemergency); setgcovalue(L, &v, udata2finalize(g)); tm = luaT_gettmbyobj(L, &v, TM_GC); - if (tm != NULL && ttisfunction(tm)) { /* is there a finalizer? */ + if (!notm(tm)) { /* is there a finalizer? */ int status; lu_byte oldah = L->allowhook; - int running = g->gcrunning; + int oldgcstp = g->gcstp; + g->gcstp |= GCSTPGC; /* avoid GC steps */ L->allowhook = 0; /* stop debug hooks during GC metamethod */ - g->gcrunning = 0; /* avoid GC steps */ - setobj2s(L, L->top, tm); /* push finalizer... */ - setobj2s(L, L->top + 1, &v); /* ... and its argument */ - L->top += 2; /* and (next line) call the finalizer */ - status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0); + setobj2s(L, L->top.p++, tm); /* push finalizer... */ + setobj2s(L, L->top.p++, &v); /* ... and its argument */ + L->ci->callstatus |= CIST_FIN; /* will run a finalizer */ + status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top.p - 2), 0); + L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ L->allowhook = oldah; /* restore hooks */ - g->gcrunning = running; /* restore state */ - if (status != LUA_OK && propagateerrors) { /* error while running __gc? */ - if (status == LUA_ERRRUN) { /* is there an error object? */ - const char *msg = (ttisstring(L->top - 1)) - ? svalue(L->top - 1) - : "no message"; - luaO_pushfstring(L, "error in __gc metamethod (%s)", msg); - status = LUA_ERRGCMM; /* error in __gc metamethod */ - } - luaD_throw(L, status); /* re-throw error */ + g->gcstp = oldgcstp; /* restore state */ + if (l_unlikely(status != LUA_OK)) { /* error while running __gc? */ + luaE_warnerror(L, "__gc"); + L->top.p--; /* pops error object */ } } } /* -** call a few (up to 'g->gcfinnum') finalizers +** Call a few finalizers */ -static int runafewfinalizers (lua_State *L) { +static int runafewfinalizers (lua_State *L, int n) { global_State *g = G(L); - unsigned int i; - lua_assert(!g->tobefnz || g->gcfinnum > 0); - for (i = 0; g->tobefnz && i < g->gcfinnum; i++) - GCTM(L, 1); /* call one finalizer */ - g->gcfinnum = (!g->tobefnz) ? 0 /* nothing more to finalize? */ - : g->gcfinnum * 2; /* else call a few more next time */ + int i; + for (i = 0; i < n && g->tobefnz; i++) + GCTM(L); /* call one finalizer */ return i; } @@ -851,10 +947,10 @@ static int runafewfinalizers (lua_State *L) { /* ** call all pending finalizers */ -static void callallpendingfinalizers (lua_State *L, int propagateerrors) { +static void callallpendingfinalizers (lua_State *L) { global_State *g = G(L); while (g->tobefnz) - GCTM(L, propagateerrors); + GCTM(L); } @@ -869,18 +965,23 @@ static GCObject **findlast (GCObject **p) { /* -** move all unreachable objects (or 'all' objects) that need -** finalization from list 'finobj' to list 'tobefnz' (to be finalized) +** Move all unreachable objects (or 'all' objects) that need +** finalization from list 'finobj' to list 'tobefnz' (to be finalized). +** (Note that objects after 'finobjold1' cannot be white, so they +** don't need to be traversed. In incremental mode, 'finobjold1' is NULL, +** so the whole list is traversed.) */ static void separatetobefnz (global_State *g, int all) { GCObject *curr; GCObject **p = &g->finobj; GCObject **lastnext = findlast(&g->tobefnz); - while ((curr = *p) != NULL) { /* traverse all finalizable objects */ + while ((curr = *p) != g->finobjold1) { /* traverse all finalizable objects */ lua_assert(tofinalize(curr)); if (!(iswhite(curr) || all)) /* not being collected? */ p = &curr->next; /* don't bother with it */ else { + if (curr == g->finobjsur) /* removing 'finobjsur'? */ + g->finobjsur = curr->next; /* correct it */ *p = curr->next; /* remove 'curr' from 'finobj' list */ curr->next = *lastnext; /* link at the end of 'tobefnz' list */ *lastnext = curr; @@ -890,6 +991,27 @@ static void separatetobefnz (global_State *g, int all) { } +/* +** If pointer 'p' points to 'o', move it to the next element. +*/ +static void checkpointer (GCObject **p, GCObject *o) { + if (o == *p) + *p = o->next; +} + + +/* +** Correct pointers to objects inside 'allgc' list when +** object 'o' is being removed from the list. +*/ +static void correctpointers (global_State *g, GCObject *o) { + checkpointer(&g->survival, o); + checkpointer(&g->old1, o); + checkpointer(&g->reallyold, o); + checkpointer(&g->firstold1, o); +} + + /* ** if object 'o' has a finalizer, remove it from 'allgc' list (must ** search the list to find it) and link it in 'finobj' list. @@ -897,15 +1019,18 @@ static void separatetobefnz (global_State *g, int all) { void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { global_State *g = G(L); if (tofinalize(o) || /* obj. is already marked... */ - gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */ + gfasttm(g, mt, TM_GC) == NULL || /* or has no finalizer... */ + (g->gcstp & GCSTPCLS)) /* or closing state? */ return; /* nothing to be done */ else { /* move 'o' to 'finobj' list */ GCObject **p; if (issweepphase(g)) { makewhite(g, o); /* "sweep" object 'o' */ if (g->sweepgc == &o->next) /* should not remove 'sweepgc' object */ - g->sweepgc = sweeptolive(L, g->sweepgc, NULL); /* change 'sweepgc' */ + g->sweepgc = sweeptolive(L, g->sweepgc); /* change 'sweepgc' */ } + else + correctpointers(g, o); /* search for pointer pointing to 'o' */ for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ } *p = o->next; /* remove 'o' from 'allgc' list */ @@ -918,181 +1043,605 @@ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { /* }====================================================== */ - /* ** {====================================================== -** GC control +** Generational Collector ** ======================================================= */ /* -** Set a reasonable "time" to wait before starting a new GC cycle; cycle -** will start when memory use hits threshold. (Division by 'estimate' -** should be OK: it cannot be zero (because Lua cannot even start with -** less than PAUSEADJ bytes). +** Set the "time" to wait before starting a new GC cycle; cycle will +** start when memory use hits the threshold of ('estimate' * pause / +** PAUSEADJ). (Division by 'estimate' should be OK: it cannot be zero, +** because Lua cannot even start with less than PAUSEADJ bytes). */ static void setpause (global_State *g) { l_mem threshold, debt; + int pause = getgcparam(g->gcpause); l_mem estimate = g->GCestimate / PAUSEADJ; /* adjust 'estimate' */ lua_assert(estimate > 0); - threshold = (g->gcpause < MAX_LMEM / estimate) /* overflow? */ - ? estimate * g->gcpause /* no overflow */ + threshold = (pause < MAX_LMEM / estimate) /* overflow? */ + ? estimate * pause /* no overflow */ : MAX_LMEM; /* overflow; truncate to maximum */ debt = gettotalbytes(g) - threshold; + if (debt > 0) debt = 0; luaE_setdebt(g, debt); } +/* +** Sweep a list of objects to enter generational mode. Deletes dead +** objects and turns the non dead to old. All non-dead threads---which +** are now old---must be in a gray list. Everything else is not in a +** gray list. Open upvalues are also kept gray. +*/ +static void sweep2old (lua_State *L, GCObject **p) { + GCObject *curr; + global_State *g = G(L); + while ((curr = *p) != NULL) { + if (iswhite(curr)) { /* is 'curr' dead? */ + lua_assert(isdead(g, curr)); + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* all surviving objects become old */ + setage(curr, G_OLD); + if (curr->tt == LUA_VTHREAD) { /* threads must be watched */ + lua_State *th = gco2th(curr); + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ + } + else if (curr->tt == LUA_VUPVAL && upisopen(gco2upv(curr))) + set2gray(curr); /* open upvalues are always gray */ + else /* everything else is black */ + nw2black(curr); + p = &curr->next; /* go to next element */ + } + } +} + + +/* +** Sweep for generational mode. Delete dead objects. (Because the +** collection is not incremental, there are no "new white" objects +** during the sweep. So, any white object must be dead.) For +** non-dead objects, advance their ages and clear the color of +** new objects. (Old objects keep their colors.) +** The ages of G_TOUCHED1 and G_TOUCHED2 objects cannot be advanced +** here, because these old-generation objects are usually not swept +** here. They will all be advanced in 'correctgraylist'. That function +** will also remove objects turned white here from any gray list. +*/ +static GCObject **sweepgen (lua_State *L, global_State *g, GCObject **p, + GCObject *limit, GCObject **pfirstold1) { + static const lu_byte nextage[] = { + G_SURVIVAL, /* from G_NEW */ + G_OLD1, /* from G_SURVIVAL */ + G_OLD1, /* from G_OLD0 */ + G_OLD, /* from G_OLD1 */ + G_OLD, /* from G_OLD (do not change) */ + G_TOUCHED1, /* from G_TOUCHED1 (do not change) */ + G_TOUCHED2 /* from G_TOUCHED2 (do not change) */ + }; + int white = luaC_white(g); + GCObject *curr; + while ((curr = *p) != limit) { + if (iswhite(curr)) { /* is 'curr' dead? */ + lua_assert(!isold(curr) && isdead(g, curr)); + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* correct mark and age */ + if (getage(curr) == G_NEW) { /* new objects go back to white */ + int marked = curr->marked & ~maskgcbits; /* erase GC bits */ + curr->marked = cast_byte(marked | G_SURVIVAL | white); + } + else { /* all other objects will be old, and so keep their color */ + setage(curr, nextage[getage(curr)]); + if (getage(curr) == G_OLD1 && *pfirstold1 == NULL) + *pfirstold1 = curr; /* first OLD1 object in the list */ + } + p = &curr->next; /* go to next element */ + } + } + return p; +} + + +/* +** Traverse a list making all its elements white and clearing their +** age. In incremental mode, all objects are 'new' all the time, +** except for fixed strings (which are always old). +*/ +static void whitelist (global_State *g, GCObject *p) { + int white = luaC_white(g); + for (; p != NULL; p = p->next) + p->marked = cast_byte((p->marked & ~maskgcbits) | white); +} + + +/* +** Correct a list of gray objects. Return pointer to where rest of the +** list should be linked. +** Because this correction is done after sweeping, young objects might +** be turned white and still be in the list. They are only removed. +** 'TOUCHED1' objects are advanced to 'TOUCHED2' and remain on the list; +** Non-white threads also remain on the list; 'TOUCHED2' objects become +** regular old; they and anything else are removed from the list. +*/ +static GCObject **correctgraylist (GCObject **p) { + GCObject *curr; + while ((curr = *p) != NULL) { + GCObject **next = getgclist(curr); + if (iswhite(curr)) + goto remove; /* remove all white objects */ + else if (getage(curr) == G_TOUCHED1) { /* touched in this cycle? */ + lua_assert(isgray(curr)); + nw2black(curr); /* make it black, for next barrier */ + changeage(curr, G_TOUCHED1, G_TOUCHED2); + goto remain; /* keep it in the list and go to next element */ + } + else if (curr->tt == LUA_VTHREAD) { + lua_assert(isgray(curr)); + goto remain; /* keep non-white threads on the list */ + } + else { /* everything else is removed */ + lua_assert(isold(curr)); /* young objects should be white here */ + if (getage(curr) == G_TOUCHED2) /* advance from TOUCHED2... */ + changeage(curr, G_TOUCHED2, G_OLD); /* ... to OLD */ + nw2black(curr); /* make object black (to be removed) */ + goto remove; + } + remove: *p = *next; continue; + remain: p = next; continue; + } + return p; +} + + +/* +** Correct all gray lists, coalescing them into 'grayagain'. +*/ +static void correctgraylists (global_State *g) { + GCObject **list = correctgraylist(&g->grayagain); + *list = g->weak; g->weak = NULL; + list = correctgraylist(list); + *list = g->allweak; g->allweak = NULL; + list = correctgraylist(list); + *list = g->ephemeron; g->ephemeron = NULL; + correctgraylist(list); +} + + +/* +** Mark black 'OLD1' objects when starting a new young collection. +** Gray objects are already in some gray list, and so will be visited +** in the atomic step. +*/ +static void markold (global_State *g, GCObject *from, GCObject *to) { + GCObject *p; + for (p = from; p != to; p = p->next) { + if (getage(p) == G_OLD1) { + lua_assert(!iswhite(p)); + changeage(p, G_OLD1, G_OLD); /* now they are old */ + if (isblack(p)) + reallymarkobject(g, p); + } + } +} + + +/* +** Finish a young-generation collection. +*/ +static void finishgencycle (lua_State *L, global_State *g) { + correctgraylists(g); + checkSizes(L, g); + g->gcstate = GCSpropagate; /* skip restart */ + if (!g->gcemergency) + callallpendingfinalizers(L); +} + + +/* +** Does a young collection. First, mark 'OLD1' objects. Then does the +** atomic step. Then, sweep all lists and advance pointers. Finally, +** finish the collection. +*/ +static void youngcollection (lua_State *L, global_State *g) { + GCObject **psurvival; /* to point to first non-dead survival object */ + GCObject *dummy; /* dummy out parameter to 'sweepgen' */ + lua_assert(g->gcstate == GCSpropagate); + if (g->firstold1) { /* are there regular OLD1 objects? */ + markold(g, g->firstold1, g->reallyold); /* mark them */ + g->firstold1 = NULL; /* no more OLD1 objects (for now) */ + } + markold(g, g->finobj, g->finobjrold); + markold(g, g->tobefnz, NULL); + atomic(L); + + /* sweep nursery and get a pointer to its last live element */ + g->gcstate = GCSswpallgc; + psurvival = sweepgen(L, g, &g->allgc, g->survival, &g->firstold1); + /* sweep 'survival' */ + sweepgen(L, g, psurvival, g->old1, &g->firstold1); + g->reallyold = g->old1; + g->old1 = *psurvival; /* 'survival' survivals are old now */ + g->survival = g->allgc; /* all news are survivals */ + + /* repeat for 'finobj' lists */ + dummy = NULL; /* no 'firstold1' optimization for 'finobj' lists */ + psurvival = sweepgen(L, g, &g->finobj, g->finobjsur, &dummy); + /* sweep 'survival' */ + sweepgen(L, g, psurvival, g->finobjold1, &dummy); + g->finobjrold = g->finobjold1; + g->finobjold1 = *psurvival; /* 'survival' survivals are old now */ + g->finobjsur = g->finobj; /* all news are survivals */ + + sweepgen(L, g, &g->tobefnz, NULL, &dummy); + finishgencycle(L, g); +} + + +/* +** Clears all gray lists, sweeps objects, and prepare sublists to enter +** generational mode. The sweeps remove dead objects and turn all +** surviving objects to old. Threads go back to 'grayagain'; everything +** else is turned black (not in any gray list). +*/ +static void atomic2gen (lua_State *L, global_State *g) { + cleargraylists(g); + /* sweep all elements making them old */ + g->gcstate = GCSswpallgc; + sweep2old(L, &g->allgc); + /* everything alive now is old */ + g->reallyold = g->old1 = g->survival = g->allgc; + g->firstold1 = NULL; /* there are no OLD1 objects anywhere */ + + /* repeat for 'finobj' lists */ + sweep2old(L, &g->finobj); + g->finobjrold = g->finobjold1 = g->finobjsur = g->finobj; + + sweep2old(L, &g->tobefnz); + + g->gckind = KGC_GEN; + g->lastatomic = 0; + g->GCestimate = gettotalbytes(g); /* base for memory control */ + finishgencycle(L, g); +} + + +/* +** Set debt for the next minor collection, which will happen when +** memory grows 'genminormul'%. +*/ +static void setminordebt (global_State *g) { + luaE_setdebt(g, -(cast(l_mem, (gettotalbytes(g) / 100)) * g->genminormul)); +} + + +/* +** Enter generational mode. Must go until the end of an atomic cycle +** to ensure that all objects are correctly marked and weak tables +** are cleared. Then, turn all objects into old and finishes the +** collection. +*/ +static lu_mem entergen (lua_State *L, global_State *g) { + lu_mem numobjs; + luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */ + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + numobjs = atomic(L); /* propagates all and then do the atomic stuff */ + atomic2gen(L, g); + setminordebt(g); /* set debt assuming next cycle will be minor */ + return numobjs; +} + + +/* +** Enter incremental mode. Turn all objects white, make all +** intermediate lists point to NULL (to avoid invalid pointers), +** and go to the pause state. +*/ +static void enterinc (global_State *g) { + whitelist(g, g->allgc); + g->reallyold = g->old1 = g->survival = NULL; + whitelist(g, g->finobj); + whitelist(g, g->tobefnz); + g->finobjrold = g->finobjold1 = g->finobjsur = NULL; + g->gcstate = GCSpause; + g->gckind = KGC_INC; + g->lastatomic = 0; +} + + +/* +** Change collector mode to 'newmode'. +*/ +void luaC_changemode (lua_State *L, int newmode) { + global_State *g = G(L); + if (newmode != g->gckind) { + if (newmode == KGC_GEN) /* entering generational mode? */ + entergen(L, g); + else + enterinc(g); /* entering incremental mode */ + } + g->lastatomic = 0; +} + + +/* +** Does a full collection in generational mode. +*/ +static lu_mem fullgen (lua_State *L, global_State *g) { + enterinc(g); + return entergen(L, g); +} + + +/* +** Does a major collection after last collection was a "bad collection". +** +** When the program is building a big structure, it allocates lots of +** memory but generates very little garbage. In those scenarios, +** the generational mode just wastes time doing small collections, and +** major collections are frequently what we call a "bad collection", a +** collection that frees too few objects. To avoid the cost of switching +** between generational mode and the incremental mode needed for full +** (major) collections, the collector tries to stay in incremental mode +** after a bad collection, and to switch back to generational mode only +** after a "good" collection (one that traverses less than 9/8 objects +** of the previous one). +** The collector must choose whether to stay in incremental mode or to +** switch back to generational mode before sweeping. At this point, it +** does not know the real memory in use, so it cannot use memory to +** decide whether to return to generational mode. Instead, it uses the +** number of objects traversed (returned by 'atomic') as a proxy. The +** field 'g->lastatomic' keeps this count from the last collection. +** ('g->lastatomic != 0' also means that the last collection was bad.) +*/ +static void stepgenfull (lua_State *L, global_State *g) { + lu_mem newatomic; /* count of traversed objects */ + lu_mem lastatomic = g->lastatomic; /* count from last collection */ + if (g->gckind == KGC_GEN) /* still in generational mode? */ + enterinc(g); /* enter incremental mode */ + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + newatomic = atomic(L); /* mark everybody */ + if (newatomic < lastatomic + (lastatomic >> 3)) { /* good collection? */ + atomic2gen(L, g); /* return to generational mode */ + setminordebt(g); + } + else { /* another bad collection; stay in incremental mode */ + g->GCestimate = gettotalbytes(g); /* first estimate */ + entersweep(L); + luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + setpause(g); + g->lastatomic = newatomic; + } +} + + +/* +** Does a generational "step". +** Usually, this means doing a minor collection and setting the debt to +** make another collection when memory grows 'genminormul'% larger. +** +** However, there are exceptions. If memory grows 'genmajormul'% +** larger than it was at the end of the last major collection (kept +** in 'g->GCestimate'), the function does a major collection. At the +** end, it checks whether the major collection was able to free a +** decent amount of memory (at least half the growth in memory since +** previous major collection). If so, the collector keeps its state, +** and the next collection will probably be minor again. Otherwise, +** we have what we call a "bad collection". In that case, set the field +** 'g->lastatomic' to signal that fact, so that the next collection will +** go to 'stepgenfull'. +** +** 'GCdebt <= 0' means an explicit call to GC step with "size" zero; +** in that case, do a minor collection. +*/ +static void genstep (lua_State *L, global_State *g) { + if (g->lastatomic != 0) /* last collection was a bad one? */ + stepgenfull(L, g); /* do a full step */ + else { + lu_mem majorbase = g->GCestimate; /* memory after last major collection */ + lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul); + if (g->GCdebt > 0 && gettotalbytes(g) > majorbase + majorinc) { + lu_mem numobjs = fullgen(L, g); /* do a major collection */ + if (gettotalbytes(g) < majorbase + (majorinc / 2)) { + /* collected at least half of memory growth since last major + collection; keep doing minor collections. */ + lua_assert(g->lastatomic == 0); + } + else { /* bad collection */ + g->lastatomic = numobjs; /* signal that last collection was bad */ + setpause(g); /* do a long wait for next (major) collection */ + } + } + else { /* regular case; do a minor collection */ + youngcollection(L, g); + setminordebt(g); + g->GCestimate = majorbase; /* preserve base value */ + } + } + lua_assert(isdecGCmodegen(g)); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** GC control +** ======================================================= +*/ + + /* ** Enter first sweep phase. -** The call to 'sweeptolive' makes pointer point to an object inside -** the list (instead of to the header), so that the real sweep do not -** need to skip objects created between "now" and the start of the real -** sweep. -** Returns how many objects it swept. +** The call to 'sweeptolive' makes the pointer point to an object +** inside the list (instead of to the header), so that the real sweep do +** not need to skip objects created between "now" and the start of the +** real sweep. */ -static int entersweep (lua_State *L) { +static void entersweep (lua_State *L) { global_State *g = G(L); - int n = 0; g->gcstate = GCSswpallgc; lua_assert(g->sweepgc == NULL); - g->sweepgc = sweeptolive(L, &g->allgc, &n); - return n; + g->sweepgc = sweeptolive(L, &g->allgc); } +/* +** Delete all objects in list 'p' until (but not including) object +** 'limit'. +*/ +static void deletelist (lua_State *L, GCObject *p, GCObject *limit) { + while (p != limit) { + GCObject *next = p->next; + freeobj(L, p); + p = next; + } +} + + +/* +** Call all finalizers of the objects in the given Lua state, and +** then free all objects, except for the main thread. +*/ void luaC_freeallobjects (lua_State *L) { global_State *g = G(L); + g->gcstp = GCSTPCLS; /* no extra finalizers after here */ + luaC_changemode(L, KGC_INC); separatetobefnz(g, 1); /* separate all objects with finalizers */ lua_assert(g->finobj == NULL); - callallpendingfinalizers(L, 0); - lua_assert(g->tobefnz == NULL); - g->currentwhite = WHITEBITS; /* this "white" makes all objects look dead */ - g->gckind = KGC_NORMAL; - sweepwholelist(L, &g->finobj); - sweepwholelist(L, &g->allgc); - sweepwholelist(L, &g->fixedgc); /* collect fixed objects */ + callallpendingfinalizers(L); + deletelist(L, g->allgc, obj2gco(g->mainthread)); + lua_assert(g->finobj == NULL); /* no new finalizers */ + deletelist(L, g->fixedgc, NULL); /* collect fixed objects */ lua_assert(g->strt.nuse == 0); } -static l_mem atomic (lua_State *L) { +static lu_mem atomic (lua_State *L) { global_State *g = G(L); - l_mem work; + lu_mem work = 0; GCObject *origweak, *origall; GCObject *grayagain = g->grayagain; /* save original list */ + g->grayagain = NULL; lua_assert(g->ephemeron == NULL && g->weak == NULL); lua_assert(!iswhite(g->mainthread)); - g->gcstate = GCSinsideatomic; - g->GCmemtrav = 0; /* start counting work */ + g->gcstate = GCSatomic; markobject(g, L); /* mark running thread */ /* registry and global metatables may be changed by API */ markvalue(g, &g->l_registry); markmt(g); /* mark global metatables */ + work += propagateall(g); /* empties 'gray' list */ /* remark occasional upvalues of (maybe) dead threads */ - remarkupvals(g); - propagateall(g); /* propagate changes */ - work = g->GCmemtrav; /* stop counting (do not recount 'grayagain') */ + work += remarkupvals(g); + work += propagateall(g); /* propagate changes */ g->gray = grayagain; - propagateall(g); /* traverse 'grayagain' list */ - g->GCmemtrav = 0; /* restart counting */ + work += propagateall(g); /* traverse 'grayagain' list */ convergeephemerons(g); /* at this point, all strongly accessible objects are marked. */ /* Clear values from weak tables, before checking finalizers */ - clearvalues(g, g->weak, NULL); - clearvalues(g, g->allweak, NULL); + clearbyvalues(g, g->weak, NULL); + clearbyvalues(g, g->allweak, NULL); origweak = g->weak; origall = g->allweak; - work += g->GCmemtrav; /* stop counting (objects being finalized) */ separatetobefnz(g, 0); /* separate objects to be finalized */ - g->gcfinnum = 1; /* there may be objects to be finalized */ - markbeingfnz(g); /* mark objects that will be finalized */ - propagateall(g); /* remark, to propagate 'resurrection' */ - g->GCmemtrav = 0; /* restart counting */ + work += markbeingfnz(g); /* mark objects that will be finalized */ + work += propagateall(g); /* remark, to propagate 'resurrection' */ convergeephemerons(g); /* at this point, all resurrected objects are marked. */ /* remove dead objects from weak tables */ - clearkeys(g, g->ephemeron, NULL); /* clear keys from all ephemeron tables */ - clearkeys(g, g->allweak, NULL); /* clear keys from all 'allweak' tables */ + clearbykeys(g, g->ephemeron); /* clear keys from all ephemeron tables */ + clearbykeys(g, g->allweak); /* clear keys from all 'allweak' tables */ /* clear values from resurrected weak tables */ - clearvalues(g, g->weak, origweak); - clearvalues(g, g->allweak, origall); + clearbyvalues(g, g->weak, origweak); + clearbyvalues(g, g->allweak, origall); luaS_clearcache(g); g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ - work += g->GCmemtrav; /* complete counting */ - return work; /* estimate of memory marked by 'atomic' */ + lua_assert(g->gray == NULL); + return work; /* estimate of slots marked by 'atomic' */ } -static lu_mem sweepstep (lua_State *L, global_State *g, - int nextstate, GCObject **nextlist) { +static int sweepstep (lua_State *L, global_State *g, + int nextstate, GCObject **nextlist) { if (g->sweepgc) { l_mem olddebt = g->GCdebt; - g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); + int count; + g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX, &count); g->GCestimate += g->GCdebt - olddebt; /* update estimate */ - if (g->sweepgc) /* is there still something to sweep? */ - return (GCSWEEPMAX * GCSWEEPCOST); + return count; + } + else { /* enter next state */ + g->gcstate = nextstate; + g->sweepgc = nextlist; + return 0; /* no work done */ } - /* else enter next state */ - g->gcstate = nextstate; - g->sweepgc = nextlist; - return 0; } static lu_mem singlestep (lua_State *L) { global_State *g = G(L); + lu_mem work; + lua_assert(!g->gcstopem); /* collector is not reentrant */ + g->gcstopem = 1; /* no emergency collections while collecting */ switch (g->gcstate) { case GCSpause: { - g->GCmemtrav = g->strt.size * sizeof(GCObject*); restartcollection(g); g->gcstate = GCSpropagate; - return g->GCmemtrav; + work = 1; + break; } case GCSpropagate: { - g->GCmemtrav = 0; - lua_assert(g->gray); - propagatemark(g); - if (g->gray == NULL) /* no more gray objects? */ - g->gcstate = GCSatomic; /* finish propagate phase */ - return g->GCmemtrav; /* memory traversed in this step */ + if (g->gray == NULL) { /* no more gray objects? */ + g->gcstate = GCSenteratomic; /* finish propagate phase */ + work = 0; + } + else + work = propagatemark(g); /* traverse one gray object */ + break; } - case GCSatomic: { - lu_mem work; - int sw; - propagateall(g); /* make sure gray list is empty */ + case GCSenteratomic: { work = atomic(L); /* work is what was traversed by 'atomic' */ - sw = entersweep(L); - g->GCestimate = gettotalbytes(g); /* first estimate */; - return work + sw * GCSWEEPCOST; + entersweep(L); + g->GCestimate = gettotalbytes(g); /* first estimate */ + break; } case GCSswpallgc: { /* sweep "regular" objects */ - return sweepstep(L, g, GCSswpfinobj, &g->finobj); + work = sweepstep(L, g, GCSswpfinobj, &g->finobj); + break; } case GCSswpfinobj: { /* sweep objects with finalizers */ - return sweepstep(L, g, GCSswptobefnz, &g->tobefnz); + work = sweepstep(L, g, GCSswptobefnz, &g->tobefnz); + break; } case GCSswptobefnz: { /* sweep objects to be finalized */ - return sweepstep(L, g, GCSswpend, NULL); + work = sweepstep(L, g, GCSswpend, NULL); + break; } case GCSswpend: { /* finish sweeps */ - makewhite(g, g->mainthread); /* sweep main thread */ checkSizes(L, g); g->gcstate = GCScallfin; - return 0; + work = 0; + break; } case GCScallfin: { /* call remaining finalizers */ - if (g->tobefnz && g->gckind != KGC_EMERGENCY) { - int n = runafewfinalizers(L); - return (n * GCFINALIZECOST); + if (g->tobefnz && !g->gcemergency) { + g->gcstopem = 0; /* ok collections during finalizers */ + work = runafewfinalizers(L, GCFINMAX) * GCFINALIZECOST; } else { /* emergency mode or no more finalizers */ g->gcstate = GCSpause; /* finish collection */ - return 0; + work = 0; } + break; } default: lua_assert(0); return 0; } + g->gcstopem = 0; + return work; } @@ -1107,69 +1656,88 @@ void luaC_runtilstate (lua_State *L, int statesmask) { } -/* -** get GC debt and convert it from Kb to 'work units' (avoid zero debt -** and overflows) -*/ -static l_mem getdebt (global_State *g) { - l_mem debt = g->GCdebt; - int stepmul = g->gcstepmul; - debt = (debt / STEPMULADJ) + 1; - debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; - return debt; -} /* -** performs a basic GC step when collector is running +** Performs a basic incremental step. The debt and step size are +** converted from bytes to "units of work"; then the function loops +** running single steps until adding that many units of work or +** finishing a cycle (pause state). Finally, it sets the debt that +** controls when next step will be performed. */ -void luaC_step (lua_State *L) { - global_State *g = G(L); - l_mem debt = getdebt(g); /* GC deficit (be paid now) */ - if (!g->gcrunning) { /* not running? */ - luaE_setdebt(g, -GCSTEPSIZE * 10); /* avoid being called too often */ - return; - } +static void incstep (lua_State *L, global_State *g) { + int stepmul = (getgcparam(g->gcstepmul) | 1); /* avoid division by 0 */ + l_mem debt = (g->GCdebt / WORK2MEM) * stepmul; + l_mem stepsize = (g->gcstepsize <= log2maxs(l_mem)) + ? ((cast(l_mem, 1) << g->gcstepsize) / WORK2MEM) * stepmul + : MAX_LMEM; /* overflow; keep maximum value */ do { /* repeat until pause or enough "credit" (negative debt) */ lu_mem work = singlestep(L); /* perform one single step */ debt -= work; - } while (debt > -GCSTEPSIZE && g->gcstate != GCSpause); + } while (debt > -stepsize && g->gcstate != GCSpause); if (g->gcstate == GCSpause) setpause(g); /* pause until next cycle */ else { - debt = (debt / g->gcstepmul) * STEPMULADJ; /* convert 'work units' to Kb */ + debt = (debt / stepmul) * WORK2MEM; /* convert 'work units' to bytes */ luaE_setdebt(g, debt); - runafewfinalizers(L); + } +} + +/* +** Performs a basic GC step if collector is running. (If collector is +** not running, set a reasonable debt to avoid it being called at +** every single check.) +*/ +void luaC_step (lua_State *L) { + global_State *g = G(L); + if (!gcrunning(g)) /* not running? */ + luaE_setdebt(g, -2000); + else { + if(isdecGCmodegen(g)) + genstep(L, g); + else + incstep(L, g); } } /* -** Performs a full GC cycle; if 'isemergency', set a flag to avoid -** some operations which could change the interpreter state in some -** unexpected ways (running finalizers and shrinking some structures). +** Perform a full collection in incremental mode. ** Before running the collection, check 'keepinvariant'; if it is true, ** there may be some objects marked as black, so the collector has ** to sweep all objects to turn them back to white (as white has not ** changed, nothing will be collected). */ -void luaC_fullgc (lua_State *L, int isemergency) { - global_State *g = G(L); - lua_assert(g->gckind == KGC_NORMAL); - if (isemergency) g->gckind = KGC_EMERGENCY; /* set flag */ - if (keepinvariant(g)) { /* black objects? */ +static void fullinc (lua_State *L, global_State *g) { + if (keepinvariant(g)) /* black objects? */ entersweep(L); /* sweep everything to turn them back to white */ - } /* finish any pending sweep phase to start a new cycle */ luaC_runtilstate(L, bitmask(GCSpause)); - luaC_runtilstate(L, ~bitmask(GCSpause)); /* start new collection */ + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + g->gcstate = GCSenteratomic; /* go straight to atomic phase */ luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ /* estimate must be correct after a full GC cycle */ lua_assert(g->GCestimate == gettotalbytes(g)); luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ - g->gckind = KGC_NORMAL; setpause(g); } + +/* +** Performs a full GC cycle; if 'isemergency', set a flag to avoid +** some operations which could change the interpreter state in some +** unexpected ways (running finalizers and shrinking some structures). +*/ +void luaC_fullgc (lua_State *L, int isemergency) { + global_State *g = G(L); + lua_assert(!g->gcemergency); + g->gcemergency = isemergency; /* set flag */ + if (g->gckind == KGC_INC) + fullinc(L, g); + else + fullgen(L, g); + g->gcemergency = 0; +} + /* }====================================================== */ diff --git a/libs/lua/lgc.h b/libs/lua/lgc.h index 0eedf842..538f6edc 100644 --- a/libs/lua/lgc.h +++ b/libs/lua/lgc.h @@ -1,5 +1,5 @@ /* -** $Id: lgc.h,v 2.86 2014/10/25 11:50:46 roberto Exp $ +** $Id: lgc.h $ ** Garbage Collector ** See Copyright Notice in lua.h */ @@ -12,38 +12,31 @@ #include "lstate.h" /* -** Collectable objects may have one of three colors: white, which -** means the object is not marked; gray, which means the -** object is marked, but its references may be not marked; and -** black, which means that the object and all its references are marked. -** The main invariant of the garbage collector, while marking objects, -** is that a black object can never point to a white one. Moreover, -** any gray object must be in a "gray list" (gray, grayagain, weak, -** allweak, ephemeron) so that it can be visited again before finishing -** the collection cycle. These lists have no meaning when the invariant -** is not being enforced (e.g., sweep phase). +** Collectable objects may have one of three colors: white, which means +** the object is not marked; gray, which means the object is marked, but +** its references may be not marked; and black, which means that the +** object and all its references are marked. The main invariant of the +** garbage collector, while marking objects, is that a black object can +** never point to a white one. Moreover, any gray object must be in a +** "gray list" (gray, grayagain, weak, allweak, ephemeron) so that it +** can be visited again before finishing the collection cycle. (Open +** upvalues are an exception to this rule.) These lists have no meaning +** when the invariant is not being enforced (e.g., sweep phase). */ - -/* how much to allocate before next GC step */ -#if !defined(GCSTEPSIZE) -/* ~100 small strings */ -#define GCSTEPSIZE (cast_int(100 * sizeof(TString))) -#endif - - /* ** Possible states of the Garbage Collector */ #define GCSpropagate 0 -#define GCSatomic 1 -#define GCSswpallgc 2 -#define GCSswpfinobj 3 -#define GCSswptobefnz 4 -#define GCSswpend 5 -#define GCScallfin 6 -#define GCSpause 7 +#define GCSenteratomic 1 +#define GCSatomic 2 +#define GCSswpallgc 3 +#define GCSswpfinobj 4 +#define GCSswptobefnz 5 +#define GCSswpend 6 +#define GCScallfin 7 +#define GCSpause 8 #define issweepphase(g) \ @@ -64,7 +57,7 @@ /* ** some useful bit tricks */ -#define resetbits(x,m) ((x) &= cast(lu_byte, ~(m))) +#define resetbits(x,m) ((x) &= cast_byte(~(m))) #define setbits(x,m) ((x) |= (m)) #define testbits(x,m) ((x) & (m)) #define bitmask(b) (1<<(b)) @@ -74,12 +67,19 @@ #define testbit(x,b) testbits(x, bitmask(b)) -/* Layout for bit use in 'marked' field: */ -#define WHITE0BIT 0 /* object is white (type 0) */ -#define WHITE1BIT 1 /* object is white (type 1) */ -#define BLACKBIT 2 /* object is black */ -#define FINALIZEDBIT 3 /* object has been marked for finalization */ -/* bit 7 is currently used by tests (luaL_checkmemory) */ +/* +** Layout for bit use in 'marked' field. First three bits are +** used for object "age" in generational mode. Last bit is used +** by tests. +*/ +#define WHITE0BIT 3 /* object is white (type 0) */ +#define WHITE1BIT 4 /* object is white (type 1) */ +#define BLACKBIT 5 /* object is black */ +#define FINALIZEDBIT 6 /* object has been marked for finalization */ + +#define TESTBIT 7 + + #define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) @@ -92,35 +92,98 @@ #define tofinalize(x) testbit((x)->marked, FINALIZEDBIT) #define otherwhite(g) ((g)->currentwhite ^ WHITEBITS) -#define isdeadm(ow,m) (!(((m) ^ WHITEBITS) & (ow))) +#define isdeadm(ow,m) ((m) & (ow)) #define isdead(g,v) isdeadm(otherwhite(g), (v)->marked) #define changewhite(x) ((x)->marked ^= WHITEBITS) -#define gray2black(x) l_setbit((x)->marked, BLACKBIT) +#define nw2black(x) \ + check_exp(!iswhite(x), l_setbit((x)->marked, BLACKBIT)) + +#define luaC_white(g) cast_byte((g)->currentwhite & WHITEBITS) + + +/* object age in generational mode */ +#define G_NEW 0 /* created in current cycle */ +#define G_SURVIVAL 1 /* created in previous cycle */ +#define G_OLD0 2 /* marked old by frw. barrier in this cycle */ +#define G_OLD1 3 /* first full cycle as old */ +#define G_OLD 4 /* really old object (not to be visited) */ +#define G_TOUCHED1 5 /* old object touched this cycle */ +#define G_TOUCHED2 6 /* old object touched in previous cycle */ + +#define AGEBITS 7 /* all age bits (111) */ + +#define getage(o) ((o)->marked & AGEBITS) +#define setage(o,a) ((o)->marked = cast_byte(((o)->marked & (~AGEBITS)) | a)) +#define isold(o) (getage(o) > G_SURVIVAL) + +#define changeage(o,f,t) \ + check_exp(getage(o) == (f), (o)->marked ^= ((f)^(t))) + + +/* Default Values for GC parameters */ +#define LUAI_GENMAJORMUL 100 +#define LUAI_GENMINORMUL 20 -#define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) +/* wait memory to double before starting new cycle */ +#define LUAI_GCPAUSE 200 +/* +** some gc parameters are stored divided by 4 to allow a maximum value +** up to 1023 in a 'lu_byte'. +*/ +#define getgcparam(p) ((p) * 4) +#define setgcparam(p,v) ((p) = (v) / 4) + +#define LUAI_GCMUL 100 + +/* how much to allocate before next GC step (log2) */ +#define LUAI_GCSTEPSIZE 13 /* 8 KB */ + + +/* +** Check whether the declared GC mode is generational. While in +** generational mode, the collector can go temporarily to incremental +** mode to improve performance. This is signaled by 'g->lastatomic != 0'. +*/ +#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0) + + +/* +** Control when GC is running: +*/ +#define GCSTPUSR 1 /* bit true when GC stopped by user */ +#define GCSTPGC 2 /* bit true when GC stopped by itself */ +#define GCSTPCLS 4 /* bit true when closing Lua state */ +#define gcrunning(g) ((g)->gcstp == 0) + + +/* +** Does one step of collection when debt becomes positive. 'pre'/'pos' +** allows some adjustments to be done only when needed. macro +** 'condchangemem' is used only for heavy tests (forcing a full +** GC cycle on every opportunity) +*/ +#define luaC_condGC(L,pre,pos) \ + { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \ + condchangemem(L,pre,pos); } -#define luaC_condGC(L,c) \ - {if (G(L)->GCdebt > 0) {c;}; condchangemem(L);} -#define luaC_checkGC(L) luaC_condGC(L, luaC_step(L);) +/* more often than not, 'pre'/'pos' are empty */ +#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0) -#define luaC_barrier(L,p,v) { \ - if (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) \ - luaC_barrier_(L,obj2gco(p),gcvalue(v)); } +#define luaC_objbarrier(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? \ + luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) -#define luaC_barrierback(L,p,v) { \ - if (iscollectable(v) && isblack(p) && iswhite(gcvalue(v))) \ - luaC_barrierback_(L,p); } +#define luaC_barrier(L,p,v) ( \ + iscollectable(v) ? luaC_objbarrier(L,p,gcvalue(v)) : cast_void(0)) -#define luaC_objbarrier(L,p,o) { \ - if (isblack(p) && iswhite(o)) \ - luaC_barrier_(L,obj2gco(p),obj2gco(o)); } +#define luaC_objbarrierback(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? luaC_barrierback_(L,p) : cast_void(0)) -#define luaC_upvalbarrier(L,uv) \ - { if (iscollectable((uv)->v) && !upisopen(uv)) \ - luaC_upvalbarrier_(L,uv); } +#define luaC_barrierback(L,p,v) ( \ + iscollectable(v) ? luaC_objbarrierback(L, p, gcvalue(v)) : cast_void(0)) LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); LUAI_FUNC void luaC_freeallobjects (lua_State *L); @@ -128,11 +191,12 @@ LUAI_FUNC void luaC_step (lua_State *L); LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask); LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); +LUAI_FUNC GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz, + size_t offset); LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); -LUAI_FUNC void luaC_barrierback_ (lua_State *L, Table *o); -LUAI_FUNC void luaC_upvalbarrier_ (lua_State *L, UpVal *uv); +LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); -LUAI_FUNC void luaC_upvdeccount (lua_State *L, UpVal *uv); +LUAI_FUNC void luaC_changemode (lua_State *L, int newmode); #endif diff --git a/libs/lua/linit.c b/libs/lua/linit.c new file mode 100644 index 00000000..69808f84 --- /dev/null +++ b/libs/lua/linit.c @@ -0,0 +1,65 @@ +/* +** $Id: linit.c $ +** Initialization of libraries for lua.c and other clients +** See Copyright Notice in lua.h +*/ + + +#define linit_c +#define LUA_LIB + +/* +** If you embed Lua in your program and need to open the standard +** libraries, call luaL_openlibs in your program. If you need a +** different set of libraries, copy this file to your project and edit +** it to suit your needs. +** +** You can also *preload* libraries, so that a later 'require' can +** open the library, which is already linked to the application. +** For that, do the following code: +** +** luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); +** lua_pushcfunction(L, luaopen_modname); +** lua_setfield(L, -2, modname); +** lua_pop(L, 1); // remove PRELOAD table +*/ + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "lualib.h" +#include "lauxlib.h" + + +/* +** these libs are loaded by lua.c and are readily available to any Lua +** program +*/ +static const luaL_Reg loadedlibs[] = { + {LUA_GNAME, luaopen_base}, + {LUA_LOADLIBNAME, luaopen_package}, + {LUA_COLIBNAME, luaopen_coroutine}, + {LUA_TABLIBNAME, luaopen_table}, + {LUA_IOLIBNAME, luaopen_io}, + {LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_UTF8LIBNAME, luaopen_utf8}, + {LUA_DBLIBNAME, luaopen_debug}, + {NULL, NULL} +}; + + +LUALIB_API void luaL_openlibs (lua_State *L) { + const luaL_Reg *lib; + /* "require" functions from 'loadedlibs' and set results to global table */ + for (lib = loadedlibs; lib->func; lib++) { + luaL_requiref(L, lib->name, lib->func, 1); + lua_pop(L, 1); /* remove lib */ + } +} + diff --git a/libs/lua/ljumptab.h b/libs/lua/ljumptab.h new file mode 100644 index 00000000..8306f250 --- /dev/null +++ b/libs/lua/ljumptab.h @@ -0,0 +1,112 @@ +/* +** $Id: ljumptab.h $ +** Jump Table for the Lua interpreter +** See Copyright Notice in lua.h +*/ + + +#undef vmdispatch +#undef vmcase +#undef vmbreak + +#define vmdispatch(x) goto *disptab[x]; + +#define vmcase(l) L_##l: + +#define vmbreak vmfetch(); vmdispatch(GET_OPCODE(i)); + + +static const void *const disptab[NUM_OPCODES] = { + +#if 0 +** you can update the following list with this command: +** +** sed -n '/^OP_/\!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h +** +#endif + +&&L_OP_MOVE, +&&L_OP_LOADI, +&&L_OP_LOADF, +&&L_OP_LOADK, +&&L_OP_LOADKX, +&&L_OP_LOADFALSE, +&&L_OP_LFALSESKIP, +&&L_OP_LOADTRUE, +&&L_OP_LOADNIL, +&&L_OP_GETUPVAL, +&&L_OP_SETUPVAL, +&&L_OP_GETTABUP, +&&L_OP_GETTABLE, +&&L_OP_GETI, +&&L_OP_GETFIELD, +&&L_OP_SETTABUP, +&&L_OP_SETTABLE, +&&L_OP_SETI, +&&L_OP_SETFIELD, +&&L_OP_NEWTABLE, +&&L_OP_SELF, +&&L_OP_ADDI, +&&L_OP_ADDK, +&&L_OP_SUBK, +&&L_OP_MULK, +&&L_OP_MODK, +&&L_OP_POWK, +&&L_OP_DIVK, +&&L_OP_IDIVK, +&&L_OP_BANDK, +&&L_OP_BORK, +&&L_OP_BXORK, +&&L_OP_SHRI, +&&L_OP_SHLI, +&&L_OP_ADD, +&&L_OP_SUB, +&&L_OP_MUL, +&&L_OP_MOD, +&&L_OP_POW, +&&L_OP_DIV, +&&L_OP_IDIV, +&&L_OP_BAND, +&&L_OP_BOR, +&&L_OP_BXOR, +&&L_OP_SHL, +&&L_OP_SHR, +&&L_OP_MMBIN, +&&L_OP_MMBINI, +&&L_OP_MMBINK, +&&L_OP_UNM, +&&L_OP_BNOT, +&&L_OP_NOT, +&&L_OP_LEN, +&&L_OP_CONCAT, +&&L_OP_CLOSE, +&&L_OP_TBC, +&&L_OP_JMP, +&&L_OP_EQ, +&&L_OP_LT, +&&L_OP_LE, +&&L_OP_EQK, +&&L_OP_EQI, +&&L_OP_LTI, +&&L_OP_LEI, +&&L_OP_GTI, +&&L_OP_GEI, +&&L_OP_TEST, +&&L_OP_TESTSET, +&&L_OP_CALL, +&&L_OP_TAILCALL, +&&L_OP_RETURN, +&&L_OP_RETURN0, +&&L_OP_RETURN1, +&&L_OP_FORLOOP, +&&L_OP_FORPREP, +&&L_OP_TFORPREP, +&&L_OP_TFORCALL, +&&L_OP_TFORLOOP, +&&L_OP_SETLIST, +&&L_OP_CLOSURE, +&&L_OP_VARARG, +&&L_OP_VARARGPREP, +&&L_OP_EXTRAARG + +}; diff --git a/libs/lua/llex.c b/libs/lua/llex.c index c35bd55f..5fc39a5c 100644 --- a/libs/lua/llex.c +++ b/libs/lua/llex.c @@ -1,5 +1,5 @@ /* -** $Id: llex.c,v 2.93 2015/05/22 17:45:56 roberto Exp $ +** $Id: llex.c $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ @@ -29,7 +29,7 @@ -#define next(ls) (ls->current = zgetc(ls->z)) +#define next(ls) (ls->current = zgetc(ls->z)) @@ -63,7 +63,7 @@ static void save (LexState *ls, int c) { newsize = luaZ_sizebuffer(b) * 2; luaZ_resizebuffer(ls->L, b, newsize); } - b->buffer[luaZ_bufflen(b)++] = cast(char, c); + b->buffer[luaZ_bufflen(b)++] = cast_char(c); } @@ -81,8 +81,10 @@ void luaX_init (lua_State *L) { const char *luaX_token2str (LexState *ls, int token) { if (token < FIRST_RESERVED) { /* single-byte symbols? */ - lua_assert(token == cast_uchar(token)); - return luaO_pushfstring(ls->L, "'%c'", token); + if (lisprint(token)) + return luaO_pushfstring(ls->L, "'%c'", token); + else /* control character */ + return luaO_pushfstring(ls->L, "'<\\%d>'", token); } else { const char *s = luaX_tokens[token - FIRST_RESERVED]; @@ -120,26 +122,29 @@ l_noret luaX_syntaxerror (LexState *ls, const char *msg) { /* -** creates a new string and anchors it in scanner's table so that -** it will not be collected until the end of the compilation -** (by that time it should be anchored somewhere) +** Creates a new string and anchors it in scanner's table so that it +** will not be collected until the end of the compilation; by that time +** it should be anchored somewhere. It also internalizes long strings, +** ensuring there is only one copy of each unique string. The table +** here is used as a set: the string enters as the key, while its value +** is irrelevant. We use the string itself as the value only because it +** is a TValue readily available. Later, the code generation can change +** this value. */ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { lua_State *L = ls->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, ls->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 */ + const TValue *o = luaH_getstr(ls->h, ts); + if (!ttisnil(o)) /* string already present? */ + ts = keystrval(nodefromval(o)); /* get saved copy */ + else { /* not in use yet */ + TValue *stv = s2v(L->top.p++); /* reserve stack space for string */ + setsvalue(L, stv, ts); /* temporarily anchor the string */ + luaH_finishset(L, ls->h, stv, o, stv); /* t[string] = string */ + /* table is not a metatable, so it does not need to invalidate cache */ luaC_checkGC(L); + L->top.p--; /* remove string from stack */ } - else { /* string already present */ - ts = tsvalue(keyfromval(o)); /* re-use value previously stored */ - } - L->top--; /* remove string from stack */ return ts; } @@ -162,7 +167,6 @@ static void inclinenumber (LexState *ls) { void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, int firstchar) { ls->t.token = 0; - ls->decpoint = '.'; ls->L = L; ls->current = firstchar; ls->lookahead.token = TK_EOS; /* no look-ahead token */ @@ -207,41 +211,18 @@ static int check_next2 (LexState *ls, const char *set) { } -/* -** change all characters 'from' in buffer to 'to' -*/ -static void buffreplace (LexState *ls, char from, char to) { - if (from != to) { - size_t n = luaZ_bufflen(ls->buff); - char *p = luaZ_buffer(ls->buff); - while (n--) - if (p[n] == from) p[n] = to; - } -} - - -#define buff2num(b,o) (luaO_str2num(luaZ_buffer(b), o) != 0) - -/* -** in case of format error, try to change decimal point separator to -** the one defined in the current locale and check again -*/ -static void trydecpoint (LexState *ls, TValue *o) { - char old = ls->decpoint; - ls->decpoint = lua_getlocaledecpoint(); - buffreplace(ls, old, ls->decpoint); /* try new decimal separator */ - if (!buff2num(ls->buff, o)) { - /* format error with correct decimal point: no more options */ - buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ - lexerror(ls, "malformed number", TK_FLT); - } -} - - /* LUA_NUMBER */ /* -** this function is quite liberal in what it accepts, as 'luaO_str2num' -** will reject ill-formed numerals. +** This function is quite liberal in what it accepts, as 'luaO_str2num' +** will reject ill-formed numerals. Roughly, it accepts the following +** pattern: +** +** %d(%x|%.|([Ee][+-]?))* | 0[Xx](%x|%.|([Pp][+-]?))* +** +** The only tricky part is to accept [+-] only after a valid exponent +** mark, to avoid reading '3-4' or '0xe+1' as a single number. +** +** The caller might have already read an initial dot. */ static int read_numeral (LexState *ls, SemInfo *seminfo) { TValue obj; @@ -252,18 +233,17 @@ static int read_numeral (LexState *ls, SemInfo *seminfo) { if (first == '0' && check_next2(ls, "xX")) /* hexadecimal? */ expo = "Pp"; for (;;) { - if (check_next2(ls, expo)) /* exponent part? */ + if (check_next2(ls, expo)) /* exponent mark? */ check_next2(ls, "-+"); /* optional exponent sign */ - if (lisxdigit(ls->current)) - save_and_next(ls); - else if (ls->current == '.') + else if (lisxdigit(ls->current) || ls->current == '.') /* '%x|%.' */ save_and_next(ls); else break; } + if (lislalpha(ls->current)) /* is numeral touching a letter? */ + save_and_next(ls); /* force an error */ save(ls, '\0'); - buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ - if (!buff2num(ls->buff, &obj)) /* format error? */ - trydecpoint(ls, &obj); /* try to update decimal point separator */ + if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */ + lexerror(ls, "malformed number", TK_FLT); if (ttisinteger(&obj)) { seminfo->i = ivalue(&obj); return TK_INT; @@ -277,12 +257,13 @@ static int read_numeral (LexState *ls, SemInfo *seminfo) { /* -** skip a sequence '[=*[' or ']=*]'; if sequence is wellformed, return -** its number of '='s; otherwise, return a negative number (-1 iff there -** are no '='s after initial bracket) +** read a sequence '[=*[' or ']=*]', leaving the last bracket. If +** sequence is well formed, return its number of '='s + 2; otherwise, +** return 1 if it is a single bracket (no '='s and no 2nd bracket); +** otherwise (an unfinished '[==...') return 0. */ -static int skip_sep (LexState *ls) { - int count = 0; +static size_t skip_sep (LexState *ls) { + size_t count = 0; int s = ls->current; lua_assert(s == '[' || s == ']'); save_and_next(ls); @@ -290,11 +271,13 @@ static int skip_sep (LexState *ls) { save_and_next(ls); count++; } - return (ls->current == s) ? count : (-count) - 1; + return (ls->current == s) ? count + 2 + : (count == 0) ? 1 + : 0; } -static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { +static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) { int line = ls->linenumber; /* initial line (for error message) */ save_and_next(ls); /* skip 2nd '[' */ if (currIsNewline(ls)) /* string starts with a newline? */ @@ -328,8 +311,8 @@ static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { } } endloop: if (seminfo) - seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep), - luaZ_bufflen(ls->buff) - 2*(2 + sep)); + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep, + luaZ_bufflen(ls->buff) - 2 * sep); } @@ -363,10 +346,10 @@ static unsigned long readutf8esc (LexState *ls) { save_and_next(ls); /* skip 'u' */ esccheck(ls, ls->current == '{', "missing '{'"); r = gethexa(ls); /* must have at least one digit */ - while ((save_and_next(ls), lisxdigit(ls->current))) { + while (cast_void(save_and_next(ls)), lisxdigit(ls->current)) { i++; + esccheck(ls, r <= (0x7FFFFFFFu >> 4), "UTF-8 value too large"); r = (r << 4) + luaO_hexavalue(ls->current); - esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large"); } esccheck(ls, ls->current == '}', "missing '}'"); next(ls); /* skip '}' */ @@ -477,9 +460,9 @@ static int llex (LexState *ls, SemInfo *seminfo) { /* else is a comment */ next(ls); if (ls->current == '[') { /* long comment? */ - int sep = skip_sep(ls); + size_t sep = skip_sep(ls); luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */ - if (sep >= 0) { + if (sep >= 2) { read_long_string(ls, NULL, sep); /* skip long comment */ luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */ break; @@ -491,45 +474,45 @@ static int llex (LexState *ls, SemInfo *seminfo) { break; } case '[': { /* long string or simply '[' */ - int sep = skip_sep(ls); - if (sep >= 0) { + size_t sep = skip_sep(ls); + if (sep >= 2) { read_long_string(ls, seminfo, sep); return TK_STRING; } - else if (sep != -1) /* '[=...' missing second bracket */ + else if (sep == 0) /* '[=...' missing second bracket? */ lexerror(ls, "invalid long string delimiter", TK_STRING); return '['; } case '=': { next(ls); - if (check_next1(ls, '=')) return TK_EQ; + if (check_next1(ls, '=')) return TK_EQ; /* '==' */ else return '='; } case '<': { next(ls); - if (check_next1(ls, '=')) return TK_LE; - else if (check_next1(ls, '<')) return TK_SHL; + if (check_next1(ls, '=')) return TK_LE; /* '<=' */ + else if (check_next1(ls, '<')) return TK_SHL; /* '<<' */ else return '<'; } case '>': { next(ls); - if (check_next1(ls, '=')) return TK_GE; - else if (check_next1(ls, '>')) return TK_SHR; + if (check_next1(ls, '=')) return TK_GE; /* '>=' */ + else if (check_next1(ls, '>')) return TK_SHR; /* '>>' */ else return '>'; } case '/': { next(ls); - if (check_next1(ls, '/')) return TK_IDIV; + if (check_next1(ls, '/')) return TK_IDIV; /* '//' */ else return '/'; } case '~': { next(ls); - if (check_next1(ls, '=')) return TK_NE; + if (check_next1(ls, '=')) return TK_NE; /* '~=' */ else return '~'; } case ':': { next(ls); - if (check_next1(ls, ':')) return TK_DBCOLON; + if (check_next1(ls, ':')) return TK_DBCOLON; /* '::' */ else return ':'; } case '"': case '\'': { /* short literal strings */ @@ -568,7 +551,7 @@ static int llex (LexState *ls, SemInfo *seminfo) { return TK_NAME; } } - else { /* single-char tokens (+ - / ...) */ + else { /* single-char tokens ('+', '*', '%', '{', '}', ...) */ int c = ls->current; next(ls); return c; diff --git a/libs/lua/llex.h b/libs/lua/llex.h index afb40b56..389d2f86 100644 --- a/libs/lua/llex.h +++ b/libs/lua/llex.h @@ -1,5 +1,5 @@ /* -** $Id: llex.h,v 1.78 2014/10/29 15:38:24 roberto Exp $ +** $Id: llex.h $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ @@ -7,11 +7,17 @@ #ifndef llex_h #define llex_h +#include + #include "lobject.h" #include "lzio.h" -#define FIRST_RESERVED 257 +/* +** Single-char tokens (terminal symbols) are represented by their own +** numeric code. Other tokens start at the following value. +*/ +#define FIRST_RESERVED (UCHAR_MAX + 1) #if !defined(LUA_ENV) @@ -37,7 +43,7 @@ enum RESERVED { }; /* number of reserved words */ -#define NUM_RESERVED (cast(int, TK_WHILE-FIRST_RESERVED+1)) +#define NUM_RESERVED (cast_int(TK_WHILE-FIRST_RESERVED + 1)) typedef union { @@ -69,7 +75,6 @@ typedef struct LexState { struct Dyndata *dyd; /* dynamic structures used by the parser */ TString *source; /* current source name */ TString *envn; /* environment variable name */ - char decpoint; /* locale decimal point */ } LexState; diff --git a/libs/lua/llimits.h b/libs/lua/llimits.h index 277c724d..1c826f7b 100644 --- a/libs/lua/llimits.h +++ b/libs/lua/llimits.h @@ -1,5 +1,5 @@ /* -** $Id: llimits.h,v 1.135 2015/06/09 14:21:00 roberto Exp $ +** $Id: llimits.h $ ** Limits, basic types, and some other 'installation-dependent' definitions ** See Copyright Notice in lua.h */ @@ -14,6 +14,7 @@ #include "lua.h" + /* ** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count ** the total memory used by Lua (in bytes). Usually, 'size_t' and @@ -22,7 +23,7 @@ #if defined(LUAI_MEM) /* { external definitions? */ typedef LUAI_UMEM lu_mem; typedef LUAI_MEM l_mem; -#elif LUAI_BITSINT >= 32 /* }{ */ +#elif LUAI_IS32INT /* }{ */ typedef size_t lu_mem; typedef ptrdiff_t l_mem; #else /* 16-bit ints */ /* }{ */ @@ -33,12 +34,13 @@ typedef long l_mem; /* chars used as small naturals (so that 'char' is reserved for characters) */ typedef unsigned char lu_byte; +typedef signed char ls_byte; /* maximum value for size_t */ #define MAX_SIZET ((size_t)(~(size_t)0)) -/* maximum size visible for Lua (must be representable in a lua_Integer */ +/* maximum size visible for Lua (must be representable in a lua_Integer) */ #define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ : (size_t)(LUA_MAXINTEGER)) @@ -52,21 +54,42 @@ typedef unsigned char lu_byte; /* -** conversion of pointer to unsigned integer: -** this is for hashing only; there is no problem if the integer -** cannot hold the whole pointer value +** floor of the log2 of the maximum signed value for integral type 't'. +** (That is, maximum 'n' such that '2^n' fits in the given signed type.) */ -#define point2uint(p) ((unsigned int)((size_t)(p) & UINT_MAX)) +#define log2maxs(t) (sizeof(t) * 8 - 2) +/* +** test whether an unsigned value is a power of 2 (or zero) +*/ +#define ispow2(x) (((x) & ((x) - 1)) == 0) -/* type to ensure maximum alignment */ -#if defined(LUAI_USER_ALIGNMENT_T) -typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; -#else -typedef union { double u; void *s; lua_Integer i; long l; } L_Umaxalign; + +/* number of chars of a literal string without the ending \0 */ +#define LL(x) (sizeof(x)/sizeof(char) - 1) + + +/* +** conversion of pointer to unsigned integer: this is for hashing only; +** there is no problem if the integer cannot hold the whole pointer +** value. (In strict ISO C this may cause undefined behavior, but no +** actual machine seems to bother.) +*/ +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(UINTPTR_MAX) /* even in C99 this type is optional */ +#define L_P2I uintptr_t +#else /* no 'intptr'? */ +#define L_P2I uintmax_t /* use the largest available integer */ +#endif +#else /* C89 option */ +#define L_P2I size_t #endif +#define point2uint(p) ((unsigned int)((L_P2I)(p) & UINT_MAX)) + /* types of 'usual argument conversions' for lua_Number and lua_Integer */ @@ -74,11 +97,19 @@ typedef LUAI_UACNUMBER l_uacNumber; typedef LUAI_UACINT l_uacInt; -/* internal assertions for in-house debugging */ +/* +** Internal assertions for in-house debugging +*/ +#if defined LUAI_ASSERT +#undef NDEBUG +#include +#define lua_assert(c) assert(c) +#endif + #if defined(lua_assert) #define check_exp(c,e) (lua_assert(c), (e)) /* to avoid problems with conditions too long */ -#define lua_longassert(c) { if (!(c)) lua_assert(0); } +#define lua_longassert(c) ((c) ? (void)0 : lua_assert(0)) #else #define lua_assert(c) ((void)0) #define check_exp(c,e) (e) @@ -89,7 +120,7 @@ typedef LUAI_UACINT l_uacInt; ** assertion for checking API calls */ #if !defined(luai_apicheck) -#define luai_apicheck(l,e) lua_assert(e) +#define luai_apicheck(l,e) ((void)l, lua_assert(e)) #endif #define api_check(l,e,msg) luai_apicheck(l,(e) && msg) @@ -105,10 +136,15 @@ typedef LUAI_UACINT l_uacInt; #define cast(t, exp) ((t)(exp)) #define cast_void(i) cast(void, (i)) -#define cast_byte(i) cast(lu_byte, (i)) +#define cast_voidp(i) cast(void *, (i)) #define cast_num(i) cast(lua_Number, (i)) #define cast_int(i) cast(int, (i)) +#define cast_uint(i) cast(unsigned int, (i)) +#define cast_byte(i) cast(lu_byte, (i)) #define cast_uchar(i) cast(unsigned char, (i)) +#define cast_char(i) cast(char, (i)) +#define cast_charp(i) cast(char *, (i)) +#define cast_sizet(i) cast(size_t, (i)) /* cast a signed lua_Integer to lua_Unsigned */ @@ -129,6 +165,8 @@ typedef LUAI_UACINT l_uacInt; /* ** non-return type */ +#if !defined(l_noret) + #if defined(__GNUC__) #define l_noret void __attribute__((noreturn)) #elif defined(_MSC_VER) && _MSC_VER >= 1200 @@ -137,28 +175,35 @@ typedef LUAI_UACINT l_uacInt; #define l_noret void #endif +#endif /* -** maximum depth for nested C calls and syntactical nested non-terminals -** in a program. (Value must fit in an unsigned short int.) +** Inline functions */ -#if !defined(LUAI_MAXCCALLS) -#define LUAI_MAXCCALLS 200 +#if !defined(LUA_USE_C89) +#define l_inline inline +#elif defined(__GNUC__) +#define l_inline __inline__ +#else +#define l_inline /* empty */ #endif +#define l_sinline static l_inline /* ** type for virtual-machine instructions; ** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) */ -#if LUAI_BITSINT >= 32 -typedef unsigned int Instruction; +#if LUAI_IS32INT +typedef unsigned int l_uint32; #else -typedef unsigned long Instruction; +typedef unsigned long l_uint32; #endif +typedef l_uint32 Instruction; + /* @@ -184,10 +229,13 @@ typedef unsigned long Instruction; /* -** Size of cache for strings in the API (better be a prime) +** Size of cache for strings in the API. 'N' is the number of +** sets (better be a prime) and "M" is the size of each set (M == 1 +** makes a direct cache.) */ -#if !defined(STRCACHE_SIZE) -#define STRCACHE_SIZE 127 +#if !defined(STRCACHE_N) +#define STRCACHE_N 53 +#define STRCACHE_M 2 #endif @@ -198,7 +246,18 @@ typedef unsigned long Instruction; /* -** macros that are executed whenether program enters the Lua core +** Maximum depth for nested C calls, syntactical nested non-terminals, +** and other features implemented through recursion in C. (Value must +** fit in a 16-bit unsigned integer. It must also be compatible with +** the size of the C stack.) +*/ +#if !defined(LUAI_MAXCCALLS) +#define LUAI_MAXCCALLS 200 +#endif + + +/* +** macros that are executed whenever program enters the Lua core ** ('lua_lock') and leaves the core ('lua_unlock') */ #if !defined(lua_lock) @@ -216,8 +275,7 @@ typedef unsigned long Instruction; /* -** these macros allow user-specific actions on threads when you defined -** LUAI_EXTRASPACE and need to do something extra when a thread is +** these macros allow user-specific actions when a thread is ** created/deleted/resumed/yielded. */ #if !defined(luai_userstateopen) @@ -261,20 +319,26 @@ typedef unsigned long Instruction; #endif /* -** modulo: defined as 'a - floor(a/b)*b'; this definition gives NaN when -** 'b' is huge, but the result should be 'a'. 'fmod' gives the result of -** 'a - trunc(a/b)*b', and therefore must be corrected when 'trunc(a/b) -** ~= floor(a/b)'. That happens when the division has a non-integer -** negative result, which is equivalent to the test below. +** modulo: defined as 'a - floor(a/b)*b'; the direct computation +** using this definition has several problems with rounding errors, +** so it is better to use 'fmod'. 'fmod' gives the result of +** 'a - trunc(a/b)*b', and therefore must be corrected when +** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a +** non-integer negative result: non-integer result is equivalent to +** a non-zero remainder 'm'; negative result is equivalent to 'a' and +** 'b' with different signs, or 'm' and 'b' with different signs +** (as the result 'm' of 'fmod' has the same sign of 'a'). */ #if !defined(luai_nummod) #define luai_nummod(L,a,b,m) \ - { (m) = l_mathop(fmod)(a,b); if ((m)*(b) < 0) (m) += (b); } + { (void)L; (m) = l_mathop(fmod)(a,b); \ + if (((m) > 0) ? (b) < 0 : ((m) < 0 && (b) > 0)) (m) += (b); } #endif /* exponentiation */ #if !defined(luai_numpow) -#define luai_numpow(L,a,b) ((void)L, l_mathop(pow)(a,b)) +#define luai_numpow(L,a,b) \ + ((void)L, (b == 2) ? (a)*(a) : l_mathop(pow)(a,b)) #endif /* the others are quite standard operations */ @@ -286,6 +350,8 @@ typedef unsigned long Instruction; #define luai_numeq(a,b) ((a)==(b)) #define luai_numlt(a,b) ((a)<(b)) #define luai_numle(a,b) ((a)<=(b)) +#define luai_numgt(a,b) ((a)>(b)) +#define luai_numge(a,b) ((a)>=(b)) #define luai_numisnan(a) (!luai_numeq((a), (a))) #endif @@ -297,17 +363,18 @@ typedef unsigned long Instruction; ** macro to control inclusion of some hard tests on stack reallocation */ #if !defined(HARDSTACKTESTS) -#define condmovestack(L) ((void)0) +#define condmovestack(L,pre,pos) ((void)0) #else /* realloc stack keeping its size */ -#define condmovestack(L) luaD_reallocstack((L), (L)->stacksize) +#define condmovestack(L,pre,pos) \ + { int sz_ = stacksize(L); pre; luaD_reallocstack((L), sz_, 0); pos; } #endif #if !defined(HARDMEMTESTS) -#define condchangemem(L) condmovestack(L) +#define condchangemem(L,pre,pos) ((void)0) #else -#define condchangemem(L) \ - ((void)(!(G(L)->gcrunning) || (luaC_fullgc(L, 0), 1))) +#define condchangemem(L,pre,pos) \ + { if (gcrunning(G(L))) { pre; luaC_fullgc(L, 0); pos; } } #endif #endif diff --git a/libs/lua/lmathlib.c b/libs/lua/lmathlib.c index 307aa498..43810634 100644 --- a/libs/lua/lmathlib.c +++ b/libs/lua/lmathlib.c @@ -1,5 +1,5 @@ /* -** $Id: lmathlib.c,v 1.115 2015/03/12 14:04:04 roberto Exp $ +** $Id: lmathlib.c $ ** Standard mathematical library ** See Copyright Notice in lua.h */ @@ -10,8 +10,11 @@ #include "lprefix.h" -#include +#include +#include #include +#include +#include #include "lua.h" @@ -23,23 +26,10 @@ #define PI (l_mathop(3.141592653589793238462643383279502884)) -#if !defined(l_rand) /* { */ -#if defined(LUA_USE_POSIX) -#define l_rand() random() -#define l_srand(x) srandom(x) -#define L_RANDMAX 2147483647 /* (2^31 - 1), following POSIX */ -#else -#define l_rand() rand() -#define l_srand(x) srand(x) -#define L_RANDMAX RAND_MAX -#endif -#endif /* } */ - - static int math_abs (lua_State *L) { if (lua_isinteger(L, 1)) { lua_Integer n = lua_tointeger(L, 1); - if (n < 0) n = (lua_Integer)(0u - n); + if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n); lua_pushinteger(L, n); } else @@ -83,11 +73,11 @@ static int math_atan (lua_State *L) { static int math_toint (lua_State *L) { int valid; lua_Integer n = lua_tointegerx(L, 1, &valid); - if (valid) + if (l_likely(valid)) lua_pushinteger(L, n); else { luaL_checkany(L, 1); - lua_pushnil(L); /* value is not convertible to integer */ + luaL_pushfail(L); /* value is not convertible to integer */ } return 1; } @@ -184,10 +174,14 @@ static int math_log (lua_State *L) { else { lua_Number base = luaL_checknumber(L, 2); #if !defined(LUA_USE_C89) - //Disabled for android if (base == 2.0) res = l_mathop(log2)(x); else + if (base == l_mathop(2.0)) + res = l_mathop(log2)(x); + else #endif - if (base == 10.0) res = l_mathop(log10)(x); - else res = l_mathop(log)(x)/l_mathop(log)(base); + if (base == l_mathop(10.0)) + res = l_mathop(log10)(x); + else + res = l_mathop(log)(x)/l_mathop(log)(base); } lua_pushnumber(L, res); return 1; @@ -236,22 +230,364 @@ static int math_max (lua_State *L) { return 1; } + +static int math_type (lua_State *L) { + if (lua_type(L, 1) == LUA_TNUMBER) + lua_pushstring(L, (lua_isinteger(L, 1)) ? "integer" : "float"); + else { + luaL_checkany(L, 1); + luaL_pushfail(L); + } + return 1; +} + + + +/* +** {================================================================== +** Pseudo-Random Number Generator based on 'xoshiro256**'. +** =================================================================== +*/ + +/* +** This code uses lots of shifts. ANSI C does not allow shifts greater +** than or equal to the width of the type being shifted, so some shifts +** are written in convoluted ways to match that restriction. For +** preprocessor tests, it assumes a width of 32 bits, so the maximum +** shift there is 31 bits. +*/ + + +/* number of binary digits in the mantissa of a float */ +#define FIGS l_floatatt(MANT_DIG) + +#if FIGS > 64 +/* there are only 64 random bits; use them all */ +#undef FIGS +#define FIGS 64 +#endif + + +/* +** LUA_RAND32 forces the use of 32-bit integers in the implementation +** of the PRN generator (mainly for testing). +*/ +#if !defined(LUA_RAND32) && !defined(Rand64) + +/* try to find an integer type with at least 64 bits */ + +#if ((ULONG_MAX >> 31) >> 31) >= 3 + +/* 'long' has at least 64 bits */ +#define Rand64 unsigned long +#define SRand64 long + +#elif !defined(LUA_USE_C89) && defined(LLONG_MAX) + +/* there is a 'long long' type (which must have at least 64 bits) */ +#define Rand64 unsigned long long +#define SRand64 long long + +#elif ((LUA_MAXUNSIGNED >> 31) >> 31) >= 3 + +/* 'lua_Unsigned' has at least 64 bits */ +#define Rand64 lua_Unsigned +#define SRand64 lua_Integer + +#endif + +#endif + + +#if defined(Rand64) /* { */ + +/* +** Standard implementation, using 64-bit integers. +** If 'Rand64' has more than 64 bits, the extra bits do not interfere +** with the 64 initial bits, except in a right shift. Moreover, the +** final result has to discard the extra bits. +*/ + +/* avoid using extra bits when needed */ +#define trim64(x) ((x) & 0xffffffffffffffffu) + + +/* rotate left 'x' by 'n' bits */ +static Rand64 rotl (Rand64 x, int n) { + return (x << n) | (trim64(x) >> (64 - n)); +} + +static Rand64 nextrand (Rand64 *state) { + Rand64 state0 = state[0]; + Rand64 state1 = state[1]; + Rand64 state2 = state[2] ^ state0; + Rand64 state3 = state[3] ^ state1; + Rand64 res = rotl(state1 * 5, 7) * 9; + state[0] = state0 ^ state3; + state[1] = state1 ^ state2; + state[2] = state2 ^ (state1 << 17); + state[3] = rotl(state3, 45); + return res; +} + + +/* +** Convert bits from a random integer into a float in the +** interval [0,1), getting the higher FIG bits from the +** random unsigned integer and converting that to a float. +** Some old Microsoft compilers cannot cast an unsigned long +** to a floating-point number, so we use a signed long as an +** intermediary. When lua_Number is float or double, the shift ensures +** that 'sx' is non negative; in that case, a good compiler will remove +** the correction. +*/ + +/* must throw out the extra (64 - FIGS) bits */ +#define shift64_FIG (64 - FIGS) + +/* 2^(-FIGS) == 2^-1 / 2^(FIGS-1) */ +#define scaleFIG (l_mathop(0.5) / ((Rand64)1 << (FIGS - 1))) + +static lua_Number I2d (Rand64 x) { + SRand64 sx = (SRand64)(trim64(x) >> shift64_FIG); + lua_Number res = (lua_Number)(sx) * scaleFIG; + if (sx < 0) + res += l_mathop(1.0); /* correct the two's complement if negative */ + lua_assert(0 <= res && res < 1); + return res; +} + +/* convert a 'Rand64' to a 'lua_Unsigned' */ +#define I2UInt(x) ((lua_Unsigned)trim64(x)) + +/* convert a 'lua_Unsigned' to a 'Rand64' */ +#define Int2I(x) ((Rand64)(x)) + + +#else /* no 'Rand64' }{ */ + +/* get an integer with at least 32 bits */ +#if LUAI_IS32INT +typedef unsigned int lu_int32; +#else +typedef unsigned long lu_int32; +#endif + + +/* +** Use two 32-bit integers to represent a 64-bit quantity. +*/ +typedef struct Rand64 { + lu_int32 h; /* higher half */ + lu_int32 l; /* lower half */ +} Rand64; + + +/* +** If 'lu_int32' has more than 32 bits, the extra bits do not interfere +** with the 32 initial bits, except in a right shift and comparisons. +** Moreover, the final result has to discard the extra bits. +*/ + +/* avoid using extra bits when needed */ +#define trim32(x) ((x) & 0xffffffffu) + + +/* +** basic operations on 'Rand64' values +*/ + +/* build a new Rand64 value */ +static Rand64 packI (lu_int32 h, lu_int32 l) { + Rand64 result; + result.h = h; + result.l = l; + return result; +} + +/* return i << n */ +static Rand64 Ishl (Rand64 i, int n) { + lua_assert(n > 0 && n < 32); + return packI((i.h << n) | (trim32(i.l) >> (32 - n)), i.l << n); +} + +/* i1 ^= i2 */ +static void Ixor (Rand64 *i1, Rand64 i2) { + i1->h ^= i2.h; + i1->l ^= i2.l; +} + +/* return i1 + i2 */ +static Rand64 Iadd (Rand64 i1, Rand64 i2) { + Rand64 result = packI(i1.h + i2.h, i1.l + i2.l); + if (trim32(result.l) < trim32(i1.l)) /* carry? */ + result.h++; + return result; +} + +/* return i * 5 */ +static Rand64 times5 (Rand64 i) { + return Iadd(Ishl(i, 2), i); /* i * 5 == (i << 2) + i */ +} + +/* return i * 9 */ +static Rand64 times9 (Rand64 i) { + return Iadd(Ishl(i, 3), i); /* i * 9 == (i << 3) + i */ +} + +/* return 'i' rotated left 'n' bits */ +static Rand64 rotl (Rand64 i, int n) { + lua_assert(n > 0 && n < 32); + return packI((i.h << n) | (trim32(i.l) >> (32 - n)), + (trim32(i.h) >> (32 - n)) | (i.l << n)); +} + +/* for offsets larger than 32, rotate right by 64 - offset */ +static Rand64 rotl1 (Rand64 i, int n) { + lua_assert(n > 32 && n < 64); + n = 64 - n; + return packI((trim32(i.h) >> n) | (i.l << (32 - n)), + (i.h << (32 - n)) | (trim32(i.l) >> n)); +} + +/* +** implementation of 'xoshiro256**' algorithm on 'Rand64' values +*/ +static Rand64 nextrand (Rand64 *state) { + Rand64 res = times9(rotl(times5(state[1]), 7)); + Rand64 t = Ishl(state[1], 17); + Ixor(&state[2], state[0]); + Ixor(&state[3], state[1]); + Ixor(&state[1], state[2]); + Ixor(&state[0], state[3]); + Ixor(&state[2], t); + state[3] = rotl1(state[3], 45); + return res; +} + + +/* +** Converts a 'Rand64' into a float. +*/ + +/* an unsigned 1 with proper type */ +#define UONE ((lu_int32)1) + + +#if FIGS <= 32 + +/* 2^(-FIGS) */ +#define scaleFIG (l_mathop(0.5) / (UONE << (FIGS - 1))) + +/* +** get up to 32 bits from higher half, shifting right to +** throw out the extra bits. +*/ +static lua_Number I2d (Rand64 x) { + lua_Number h = (lua_Number)(trim32(x.h) >> (32 - FIGS)); + return h * scaleFIG; +} + +#else /* 32 < FIGS <= 64 */ + +/* 2^(-FIGS) = 1.0 / 2^30 / 2^3 / 2^(FIGS-33) */ +#define scaleFIG \ + (l_mathop(1.0) / (UONE << 30) / l_mathop(8.0) / (UONE << (FIGS - 33))) + +/* +** use FIGS - 32 bits from lower half, throwing out the other +** (32 - (FIGS - 32)) = (64 - FIGS) bits +*/ +#define shiftLOW (64 - FIGS) + +/* +** higher 32 bits go after those (FIGS - 32) bits: shiftHI = 2^(FIGS - 32) +*/ +#define shiftHI ((lua_Number)(UONE << (FIGS - 33)) * l_mathop(2.0)) + + +static lua_Number I2d (Rand64 x) { + lua_Number h = (lua_Number)trim32(x.h) * shiftHI; + lua_Number l = (lua_Number)(trim32(x.l) >> shiftLOW); + return (h + l) * scaleFIG; +} + +#endif + + +/* convert a 'Rand64' to a 'lua_Unsigned' */ +static lua_Unsigned I2UInt (Rand64 x) { + return (((lua_Unsigned)trim32(x.h) << 31) << 1) | (lua_Unsigned)trim32(x.l); +} + +/* convert a 'lua_Unsigned' to a 'Rand64' */ +static Rand64 Int2I (lua_Unsigned n) { + return packI((lu_int32)((n >> 31) >> 1), (lu_int32)n); +} + +#endif /* } */ + + +/* +** A state uses four 'Rand64' values. +*/ +typedef struct { + Rand64 s[4]; +} RanState; + + /* -** This function uses 'double' (instead of 'lua_Number') to ensure that -** all bits from 'l_rand' can be represented, and that 'RANDMAX + 1.0' -** will keep full precision (ensuring that 'r' is always less than 1.0.) +** Project the random integer 'ran' into the interval [0, n]. +** Because 'ran' has 2^B possible values, the projection can only be +** uniform when the size of the interval is a power of 2 (exact +** division). Otherwise, to get a uniform projection into [0, n], we +** first compute 'lim', the smallest Mersenne number not smaller than +** 'n'. We then project 'ran' into the interval [0, lim]. If the result +** is inside [0, n], we are done. Otherwise, we try with another 'ran', +** until we have a result inside the interval. */ +static lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n, + RanState *state) { + if ((n & (n + 1)) == 0) /* is 'n + 1' a power of 2? */ + return ran & n; /* no bias */ + else { + lua_Unsigned lim = n; + /* compute the smallest (2^b - 1) not smaller than 'n' */ + lim |= (lim >> 1); + lim |= (lim >> 2); + lim |= (lim >> 4); + lim |= (lim >> 8); + lim |= (lim >> 16); +#if (LUA_MAXUNSIGNED >> 31) >= 3 + lim |= (lim >> 32); /* integer type has more than 32 bits */ +#endif + lua_assert((lim & (lim + 1)) == 0 /* 'lim + 1' is a power of 2, */ + && lim >= n /* not smaller than 'n', */ + && (lim >> 1) < n); /* and it is the smallest one */ + while ((ran &= lim) > n) /* project 'ran' into [0..lim] */ + ran = I2UInt(nextrand(state->s)); /* not inside [0..n]? try again */ + return ran; + } +} + + static int math_random (lua_State *L) { lua_Integer low, up; - double r = (double)l_rand() * (1.0 / ((double)L_RANDMAX + 1.0)); + lua_Unsigned p; + RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1)); + Rand64 rv = nextrand(state->s); /* next pseudo-random value */ switch (lua_gettop(L)) { /* check number of arguments */ case 0: { /* no arguments */ - lua_pushnumber(L, (lua_Number)r); /* Number between 0 and 1 */ + lua_pushnumber(L, I2d(rv)); /* float between 0 and 1 */ return 1; } case 1: { /* only upper limit */ low = 1; up = luaL_checkinteger(L, 1); + if (up == 0) { /* single 0 as argument? */ + lua_pushinteger(L, I2UInt(rv)); /* full random integer */ + return 1; + } break; } case 2: { /* lower and upper limits */ @@ -262,43 +598,80 @@ static int math_random (lua_State *L) { default: return luaL_error(L, "wrong number of arguments"); } /* random integer in the interval [low, up] */ - luaL_argcheck(L, low <= up, 1, "interval is empty"); - luaL_argcheck(L, low >= 0 || up <= LUA_MAXINTEGER + low, 1, - "interval too large"); - r *= (double)(up - low) + 1.0; - lua_pushinteger(L, (lua_Integer)r + low); + luaL_argcheck(L, low <= up, 1, "interval is empty"); + /* project random integer into the interval [0, up - low] */ + p = project(I2UInt(rv), (lua_Unsigned)up - (lua_Unsigned)low, state); + lua_pushinteger(L, p + (lua_Unsigned)low); return 1; } -static int math_randomseed (lua_State *L) { - l_srand((unsigned int)(lua_Integer)luaL_checknumber(L, 1)); - (void)rand(); /* discard first value to avoid undesirable correlations */ - return 0; +static void setseed (lua_State *L, Rand64 *state, + lua_Unsigned n1, lua_Unsigned n2) { + int i; + state[0] = Int2I(n1); + state[1] = Int2I(0xff); /* avoid a zero state */ + state[2] = Int2I(n2); + state[3] = Int2I(0); + for (i = 0; i < 16; i++) + nextrand(state); /* discard initial values to "spread" seed */ + lua_pushinteger(L, n1); + lua_pushinteger(L, n2); } -static int math_type (lua_State *L) { - if (lua_type(L, 1) == LUA_TNUMBER) { - if (lua_isinteger(L, 1)) - lua_pushliteral(L, "integer"); - else - lua_pushliteral(L, "float"); +/* +** Set a "random" seed. To get some randomness, use the current time +** and the address of 'L' (in case the machine does address space layout +** randomization). +*/ +static void randseed (lua_State *L, RanState *state) { + lua_Unsigned seed1 = (lua_Unsigned)time(NULL); + lua_Unsigned seed2 = (lua_Unsigned)(size_t)L; + setseed(L, state->s, seed1, seed2); +} + + +static int math_randomseed (lua_State *L) { + RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1)); + if (lua_isnone(L, 1)) { + randseed(L, state); } else { - luaL_checkany(L, 1); - lua_pushnil(L); + lua_Integer n1 = luaL_checkinteger(L, 1); + lua_Integer n2 = luaL_optinteger(L, 2, 0); + setseed(L, state->s, n1, n2); } - return 1; + return 2; /* return seeds */ } +static const luaL_Reg randfuncs[] = { + {"random", math_random}, + {"randomseed", math_randomseed}, + {NULL, NULL} +}; + + +/* +** Register the random functions and initialize their state. +*/ +static void setrandfunc (lua_State *L) { + RanState *state = (RanState *)lua_newuserdatauv(L, sizeof(RanState), 0); + randseed(L, state); /* initialize with a "random" seed */ + lua_pop(L, 2); /* remove pushed seeds */ + luaL_setfuncs(L, randfuncs, 1); +} + +/* }================================================================== */ + + /* ** {================================================================== ** Deprecated functions (for compatibility only) ** =================================================================== */ -//#if defined(LUA_COMPAT_MATHLIB) +#if defined(LUA_COMPAT_MATHLIB) static int math_cosh (lua_State *L) { lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); @@ -341,7 +714,7 @@ static int math_log10 (lua_State *L) { return 1; } -//#endif +#endif /* }================================================================== */ @@ -364,13 +737,11 @@ static const luaL_Reg mathlib[] = { {"min", math_min}, {"modf", math_modf}, {"rad", math_rad}, - {"random", math_random}, - {"randomseed", math_randomseed}, {"sin", math_sin}, {"sqrt", math_sqrt}, {"tan", math_tan}, {"type", math_type}, -//#if defined(LUA_COMPAT_MATHLIB) +#if defined(LUA_COMPAT_MATHLIB) {"atan2", math_atan}, {"cosh", math_cosh}, {"sinh", math_sinh}, @@ -379,8 +750,10 @@ static const luaL_Reg mathlib[] = { {"frexp", math_frexp}, {"ldexp", math_ldexp}, {"log10", math_log10}, -//#endif +#endif /* placeholders */ + {"random", NULL}, + {"randomseed", NULL}, {"pi", NULL}, {"huge", NULL}, {"maxinteger", NULL}, @@ -402,6 +775,7 @@ LUAMOD_API int luaopen_math (lua_State *L) { lua_setfield(L, -2, "maxinteger"); lua_pushinteger(L, LUA_MININTEGER); lua_setfield(L, -2, "mininteger"); + setrandfunc(L); return 1; } diff --git a/libs/lua/lmem.c b/libs/lua/lmem.c index 0a0476cc..9800a86f 100644 --- a/libs/lua/lmem.c +++ b/libs/lua/lmem.c @@ -1,5 +1,5 @@ /* -** $Id: lmem.c,v 1.91 2015/03/06 19:45:54 roberto Exp $ +** $Id: lmem.c $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ @@ -25,76 +25,191 @@ /* ** About the realloc function: -** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize); +** void *frealloc (void *ud, void *ptr, size_t osize, size_t nsize); ** ('osize' is the old size, 'nsize' is the new size) ** -** * frealloc(ud, NULL, x, s) creates a new block of size 's' (no -** matter 'x'). +** - frealloc(ud, p, x, 0) frees the block 'p' and returns NULL. +** Particularly, frealloc(ud, NULL, 0, 0) does nothing, +** which is equivalent to free(NULL) in ISO C. ** -** * frealloc(ud, p, x, 0) frees the block 'p' -** (in this specific case, frealloc must return NULL); -** particularly, frealloc(ud, NULL, 0, 0) does nothing -** (which is equivalent to free(NULL) in ISO C) +** - frealloc(ud, NULL, x, s) creates a new block of size 's' +** (no matter 'x'). Returns NULL if it cannot create the new block. ** -** frealloc returns NULL if it cannot create or reallocate the area -** (any reallocation to an equal or smaller size cannot fail!) +** - otherwise, frealloc(ud, b, x, y) reallocates the block 'b' from +** size 'x' to size 'y'. Returns NULL if it cannot reallocate the +** block to the new size. */ +/* +** Macro to call the allocation function. +*/ +#define callfrealloc(g,block,os,ns) ((*g->frealloc)(g->ud, block, os, ns)) + + +/* +** When an allocation fails, it will try again after an emergency +** collection, except when it cannot run a collection. The GC should +** not be called while the state is not fully built, as the collector +** is not yet fully initialized. Also, it should not be called when +** 'gcstopem' is true, because then the interpreter is in the middle of +** a collection step. +*/ +#define cantryagain(g) (completestate(g) && !g->gcstopem) + + + + +#if defined(EMERGENCYGCTESTS) +/* +** First allocation will fail except when freeing a block (frees never +** fail) and when it cannot try again; this fail will trigger 'tryagain' +** and a full GC cycle at every allocation. +*/ +static void *firsttry (global_State *g, void *block, size_t os, size_t ns) { + if (ns > 0 && cantryagain(g)) + return NULL; /* fail */ + else /* normal allocation */ + return callfrealloc(g, block, os, ns); +} +#else +#define firsttry(g,block,os,ns) callfrealloc(g, block, os, ns) +#endif + + + + +/* +** {================================================================== +** Functions to allocate/deallocate arrays for the Parser +** =================================================================== +*/ + +/* +** Minimum size for arrays during parsing, to avoid overhead of +** reallocating to size 1, then 2, and then 4. All these arrays +** will be reallocated to exact sizes or erased when parsing ends. +*/ #define MINSIZEARRAY 4 -void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems, - int limit, const char *what) { +void *luaM_growaux_ (lua_State *L, void *block, int nelems, int *psize, + int size_elems, int limit, const char *what) { void *newblock; - int newsize; - if (*size >= limit/2) { /* cannot double it? */ - if (*size >= limit) /* cannot grow even a little? */ + int size = *psize; + if (nelems + 1 <= size) /* does one extra element still fit? */ + return block; /* nothing to be done */ + if (size >= limit / 2) { /* cannot double it? */ + if (l_unlikely(size >= limit)) /* cannot grow even a little? */ luaG_runerror(L, "too many %s (limit is %d)", what, limit); - newsize = limit; /* still have at least one free place */ + size = limit; /* still have at least one free place */ } else { - newsize = (*size)*2; - if (newsize < MINSIZEARRAY) - newsize = MINSIZEARRAY; /* minimum size */ + size *= 2; + if (size < MINSIZEARRAY) + size = MINSIZEARRAY; /* minimum size */ } - newblock = luaM_reallocv(L, block, *size, newsize, size_elems); - *size = newsize; /* update only when everything else is OK */ + lua_assert(nelems + 1 <= size && size <= limit); + /* 'limit' ensures that multiplication will not overflow */ + newblock = luaM_saferealloc_(L, block, cast_sizet(*psize) * size_elems, + cast_sizet(size) * size_elems); + *psize = size; /* update only when everything else is OK */ + return newblock; +} + + +/* +** In prototypes, the size of the array is also its number of +** elements (to save memory). So, if it cannot shrink an array +** to its number of elements, the only option is to raise an +** error. +*/ +void *luaM_shrinkvector_ (lua_State *L, void *block, int *size, + int final_n, int size_elem) { + void *newblock; + size_t oldsize = cast_sizet((*size) * size_elem); + size_t newsize = cast_sizet(final_n * size_elem); + lua_assert(newsize <= oldsize); + newblock = luaM_saferealloc_(L, block, oldsize, newsize); + *size = final_n; return newblock; } +/* }================================================================== */ + l_noret luaM_toobig (lua_State *L) { luaG_runerror(L, "memory allocation error: block too big"); } +/* +** Free memory +*/ +void luaM_free_ (lua_State *L, void *block, size_t osize) { + global_State *g = G(L); + lua_assert((osize == 0) == (block == NULL)); + callfrealloc(g, block, osize, 0); + g->GCdebt -= osize; +} + /* -** generic allocation routine. +** In case of allocation fail, this function will do an emergency +** collection to free some memory and then try the allocation again. +*/ +static void *tryagain (lua_State *L, void *block, + size_t osize, size_t nsize) { + global_State *g = G(L); + if (cantryagain(g)) { + luaC_fullgc(L, 1); /* try to free some memory... */ + return callfrealloc(g, block, osize, nsize); /* try again */ + } + else return NULL; /* cannot run an emergency collection */ +} + + +/* +** Generic allocation routine. */ void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { void *newblock; global_State *g = G(L); - size_t realosize = (block) ? osize : 0; - lua_assert((realosize == 0) == (block == NULL)); -#if defined(HARDMEMTESTS) - if (nsize > realosize && g->gcrunning) - luaC_fullgc(L, 1); /* force a GC whenever possible */ -#endif - newblock = (*g->frealloc)(g->ud, block, osize, nsize); - if (newblock == NULL && nsize > 0) { - lua_assert(nsize > realosize); /* cannot fail when shrinking a block */ - if (g->version) { /* is state fully built? */ - luaC_fullgc(L, 1); /* try to free some memory... */ - newblock = (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ - } - if (newblock == NULL) - luaD_throw(L, LUA_ERRMEM); + lua_assert((osize == 0) == (block == NULL)); + newblock = firsttry(g, block, osize, nsize); + if (l_unlikely(newblock == NULL && nsize > 0)) { + newblock = tryagain(L, block, osize, nsize); + if (newblock == NULL) /* still no memory? */ + return NULL; /* do not update 'GCdebt' */ } lua_assert((nsize == 0) == (newblock == NULL)); - g->GCdebt = (g->GCdebt + nsize) - realosize; + g->GCdebt = (g->GCdebt + nsize) - osize; return newblock; } + +void *luaM_saferealloc_ (lua_State *L, void *block, size_t osize, + size_t nsize) { + void *newblock = luaM_realloc_(L, block, osize, nsize); + if (l_unlikely(newblock == NULL && nsize > 0)) /* allocation failed? */ + luaM_error(L); + return newblock; +} + + +void *luaM_malloc_ (lua_State *L, size_t size, int tag) { + if (size == 0) + return NULL; /* that's all */ + else { + global_State *g = G(L); + void *newblock = firsttry(g, NULL, tag, size); + if (l_unlikely(newblock == NULL)) { + newblock = tryagain(L, NULL, tag, size); + if (newblock == NULL) + luaM_error(L); + } + g->GCdebt += size; + return newblock; + } +} diff --git a/libs/lua/lmem.h b/libs/lua/lmem.h index 30f48489..8c75a44b 100644 --- a/libs/lua/lmem.h +++ b/libs/lua/lmem.h @@ -1,5 +1,5 @@ /* -** $Id: lmem.h,v 1.43 2014/12/19 17:26:14 roberto Exp $ +** $Id: lmem.h $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ @@ -14,12 +14,13 @@ #include "lua.h" +#define luaM_error(L) luaD_throw(L, LUA_ERRMEM) + + /* -** This macro reallocs a vector 'b' from 'on' to 'n' elements, where -** each element has size 'e'. In case of arithmetic overflow of the -** product 'n'*'e', it raises an error (calling 'luaM_toobig'). Because -** 'e' is always constant, it avoids the runtime division MAX_SIZET/(e). -** +** This macro tests whether it is safe to multiply 'n' by the size of +** type 't' without overflows. Because 'e' is always constant, it avoids +** the runtime division MAX_SIZET/(e). ** (The macro is somewhat complex to avoid warnings: The 'sizeof' ** comparison avoids a runtime comparison when overflow cannot occur. ** The compiler should be able to optimize the real test by itself, but @@ -27,43 +28,66 @@ ** false due to limited range of data type"; the +1 tricks the compiler, ** avoiding this warning but also this optimization.) */ -#define luaM_reallocv(L,b,on,n,e) \ - (((sizeof(n) >= sizeof(size_t) && cast(size_t, (n)) + 1 > MAX_SIZET/(e)) \ - ? luaM_toobig(L) : cast_void(0)) , \ - luaM_realloc_(L, (b), (on)*(e), (n)*(e))) +#define luaM_testsize(n,e) \ + (sizeof(n) >= sizeof(size_t) && cast_sizet((n)) + 1 > MAX_SIZET/(e)) + +#define luaM_checksize(L,n,e) \ + (luaM_testsize(n,e) ? luaM_toobig(L) : cast_void(0)) + + +/* +** Computes the minimum between 'n' and 'MAX_SIZET/sizeof(t)', so that +** the result is not larger than 'n' and cannot overflow a 'size_t' +** when multiplied by the size of type 't'. (Assumes that 'n' is an +** 'int' or 'unsigned int' and that 'int' is not larger than 'size_t'.) +*/ +#define luaM_limitN(n,t) \ + ((cast_sizet(n) <= MAX_SIZET/sizeof(t)) ? (n) : \ + cast_uint((MAX_SIZET/sizeof(t)))) + /* ** Arrays of chars do not need any test */ #define luaM_reallocvchar(L,b,on,n) \ - cast(char *, luaM_realloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char))) + cast_charp(luaM_saferealloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char))) -#define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0) -#define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0) -#define luaM_freearray(L, b, n) luaM_realloc_(L, (b), (n)*sizeof(*(b)), 0) +#define luaM_freemem(L, b, s) luaM_free_(L, (b), (s)) +#define luaM_free(L, b) luaM_free_(L, (b), sizeof(*(b))) +#define luaM_freearray(L, b, n) luaM_free_(L, (b), (n)*sizeof(*(b))) -#define luaM_malloc(L,s) luaM_realloc_(L, NULL, 0, (s)) -#define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t))) -#define luaM_newvector(L,n,t) \ - cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t))) +#define luaM_new(L,t) cast(t*, luaM_malloc_(L, sizeof(t), 0)) +#define luaM_newvector(L,n,t) cast(t*, luaM_malloc_(L, (n)*sizeof(t), 0)) +#define luaM_newvectorchecked(L,n,t) \ + (luaM_checksize(L,n,sizeof(t)), luaM_newvector(L,n,t)) -#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s)) +#define luaM_newobject(L,tag,s) luaM_malloc_(L, (s), tag) #define luaM_growvector(L,v,nelems,size,t,limit,e) \ - if ((nelems)+1 > (size)) \ - ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e))) + ((v)=cast(t *, luaM_growaux_(L,v,nelems,&(size),sizeof(t), \ + luaM_limitN(limit,t),e))) #define luaM_reallocvector(L, v,oldn,n,t) \ - ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t)))) + (cast(t *, luaM_realloc_(L, v, cast_sizet(oldn) * sizeof(t), \ + cast_sizet(n) * sizeof(t)))) + +#define luaM_shrinkvector(L,v,size,fs,t) \ + ((v)=cast(t *, luaM_shrinkvector_(L, v, &(size), fs, sizeof(t)))) LUAI_FUNC l_noret luaM_toobig (lua_State *L); /* not to be called directly */ LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize, size_t size); -LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size, - size_t size_elem, int limit, +LUAI_FUNC void *luaM_saferealloc_ (lua_State *L, void *block, size_t oldsize, + size_t size); +LUAI_FUNC void luaM_free_ (lua_State *L, void *block, size_t osize); +LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int nelems, + int *size, int size_elem, int limit, const char *what); +LUAI_FUNC void *luaM_shrinkvector_ (lua_State *L, void *block, int *nelem, + int final_n, int size_elem); +LUAI_FUNC void *luaM_malloc_ (lua_State *L, size_t size, int tag); #endif diff --git a/libs/lua/loadlib.c b/libs/lua/loadlib.c index bbf8f67a..6d289fce 100644 --- a/libs/lua/loadlib.c +++ b/libs/lua/loadlib.c @@ -1,5 +1,5 @@ /* -** $Id: loadlib.c,v 1.126 2015/02/16 13:14:33 roberto Exp $ +** $Id: loadlib.c $ ** Dynamic library loader for Lua ** See Copyright Notice in lua.h ** @@ -24,46 +24,6 @@ #include "lualib.h" -/* -** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment -** variables that Lua check to set its paths. -*/ -#if !defined(LUA_PATH_VAR) -#define LUA_PATH_VAR "LUA_PATH" -#endif - -#if !defined(LUA_CPATH_VAR) -#define LUA_CPATH_VAR "LUA_CPATH" -#endif - -#define LUA_PATHSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR - -#define LUA_PATHVARVERSION LUA_PATH_VAR LUA_PATHSUFFIX -#define LUA_CPATHVARVERSION LUA_CPATH_VAR LUA_PATHSUFFIX - -/* -** 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. -** LUA_IGMARK is a mark to ignore all before it when building the -** luaopen_ function name. -*/ -#if !defined (LUA_PATH_SEP) -#define LUA_PATH_SEP ";" -#endif -#if !defined (LUA_PATH_MARK) -#define LUA_PATH_MARK "?" -#endif -#if !defined (LUA_EXEC_DIR) -#define LUA_EXEC_DIR "!" -#endif -#if !defined (LUA_IGMARK) -#define LUA_IGMARK "-" -#endif - - /* ** LUA_CSUBSEP is the character that replaces dots in submodule names ** when searching for a C loader. @@ -87,14 +47,22 @@ /* -** unique key for table in the registry that keeps handles +** key for table in the registry that keeps handles ** for all loaded C libraries */ -static const int CLIBS = 0; +static const char *const CLIBS = "_CLIBS"; #define LIB_FAIL "open" -#define setprogdir(L) ((void)0) + +#define setprogdir(L) ((void)0) + + +/* +** Special type equivalent to '(void*)' for functions in gcc +** (to suppress warnings when converting function pointers) +*/ +typedef void (*voidf)(void); /* @@ -155,14 +123,16 @@ static void lsys_unloadlib (void *lib) { static void *lsys_load (lua_State *L, const char *path, int seeglb) { void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL)); - if (lib == NULL) lua_pushstring(L, dlerror()); + if (l_unlikely(lib == NULL)) + lua_pushstring(L, dlerror()); return lib; } static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { lua_CFunction f = cast_func(dlsym(lib, sym)); - if (f == NULL) lua_pushstring(L, dlerror()); + if (l_unlikely(f == NULL)) + lua_pushstring(L, dlerror()); return f; } @@ -179,7 +149,6 @@ static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { #include -#undef setprogdir /* ** optional flags for LoadLibraryEx @@ -189,21 +158,30 @@ static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { #endif +#undef setprogdir + + +/* +** Replace in the path (on the top of the stack) any occurrence +** of LUA_EXEC_DIR with the executable's path. +*/ static void setprogdir (lua_State *L) { char buff[MAX_PATH + 1]; char *lb; DWORD nsize = sizeof(buff)/sizeof(char); - DWORD n = GetModuleFileNameA(NULL, buff, nsize); + DWORD n = GetModuleFileNameA(NULL, buff, nsize); /* get exec. name */ if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL) luaL_error(L, "unable to get ModuleFileName"); else { - *lb = '\0'; + *lb = '\0'; /* cut name on the last '\\' to get the path */ luaL_gsub(L, lua_tostring(L, -1), LUA_EXEC_DIR, buff); lua_remove(L, -2); /* remove original string */ } } + + static void pusherror (lua_State *L) { int error = GetLastError(); char buffer[128]; @@ -228,7 +206,7 @@ static void *lsys_load (lua_State *L, const char *path, int seeglb) { static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { - lua_CFunction f = (lua_CFunction)GetProcAddress((HMODULE)lib, sym); + lua_CFunction f = (lua_CFunction)(voidf)GetProcAddress((HMODULE)lib, sym); if (f == NULL) pusherror(L); return f; } @@ -272,12 +250,82 @@ static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { #endif /* } */ +/* +** {================================================================== +** Set Paths +** =================================================================== +*/ + +/* +** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment +** variables that Lua check to set its paths. +*/ +#if !defined(LUA_PATH_VAR) +#define LUA_PATH_VAR "LUA_PATH" +#endif + +#if !defined(LUA_CPATH_VAR) +#define LUA_CPATH_VAR "LUA_CPATH" +#endif + + + +/* +** return registry.LUA_NOENV as a boolean +*/ +static int noenv (lua_State *L) { + int b; + lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); + b = lua_toboolean(L, -1); + lua_pop(L, 1); /* remove value */ + return b; +} + + +/* +** Set a path +*/ +static void setpath (lua_State *L, const char *fieldname, + const char *envname, + const char *dft) { + const char *dftmark; + const char *nver = lua_pushfstring(L, "%s%s", envname, LUA_VERSUFFIX); + const char *path = getenv(nver); /* try versioned name */ + if (path == NULL) /* no versioned environment variable? */ + path = getenv(envname); /* try unversioned name */ + if (path == NULL || noenv(L)) /* no environment variable? */ + lua_pushstring(L, dft); /* use default */ + else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL) + lua_pushstring(L, path); /* nothing to change */ + else { /* path contains a ";;": insert default path in its place */ + size_t len = strlen(path); + luaL_Buffer b; + luaL_buffinit(L, &b); + if (path < dftmark) { /* is there a prefix before ';;'? */ + luaL_addlstring(&b, path, dftmark - path); /* add it */ + luaL_addchar(&b, *LUA_PATH_SEP); + } + luaL_addstring(&b, dft); /* add default */ + if (dftmark < path + len - 2) { /* is there a suffix after ';;'? */ + luaL_addchar(&b, *LUA_PATH_SEP); + luaL_addlstring(&b, dftmark + 2, (path + len - 2) - dftmark); + } + luaL_pushresult(&b); + } + setprogdir(L); + lua_setfield(L, -3, fieldname); /* package[fieldname] = path value */ + lua_pop(L, 1); /* pop versioned variable name ('nver') */ +} + +/* }================================================================== */ + + /* ** return registry.CLIBS[path] */ static void *checkclib (lua_State *L, const char *path) { void *plib; - lua_rawgetp(L, LUA_REGISTRYINDEX, &CLIBS); + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); lua_getfield(L, -1, path); plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */ lua_pop(L, 2); /* pop CLIBS table and 'plib' */ @@ -290,7 +338,7 @@ static void *checkclib (lua_State *L, const char *path) { ** registry.CLIBS[#CLIBS + 1] = plib -- also keep a list of all libraries */ static void addtoclib (lua_State *L, const char *path, void *plib) { - lua_rawgetp(L, LUA_REGISTRYINDEX, &CLIBS); + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); lua_pushlightuserdata(L, plib); lua_pushvalue(L, -1); lua_setfield(L, -3, path); /* CLIBS[path] = plib */ @@ -355,13 +403,13 @@ static int ll_loadlib (lua_State *L) { const char *path = luaL_checkstring(L, 1); const char *init = luaL_checkstring(L, 2); int stat = lookforfunc(L, path, init); - if (stat == 0) /* no errors? */ + if (l_likely(stat == 0)) /* no errors? */ return 1; /* return the loaded function */ else { /* error; error message is on stack top */ - lua_pushnil(L); + luaL_pushfail(L); lua_insert(L, -2); lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init"); - return 3; /* return nil, error message, and where */ + return 3; /* return fail, error message, and where */ } } @@ -382,14 +430,42 @@ static int readable (const char *filename) { } -static const char *pushnexttemplate (lua_State *L, const char *path) { - const char *l; - while (*path == *LUA_PATH_SEP) path++; /* skip separators */ - if (*path == '\0') return NULL; /* no more templates */ - l = strchr(path, *LUA_PATH_SEP); /* find next separator */ - if (l == NULL) l = path + strlen(path); - lua_pushlstring(L, path, l - path); /* template */ - return l; +/* +** Get the next name in '*path' = 'name1;name2;name3;...', changing +** the ending ';' to '\0' to create a zero-terminated string. Return +** NULL when list ends. +*/ +static const char *getnextfilename (char **path, char *end) { + char *sep; + char *name = *path; + if (name == end) + return NULL; /* no more names */ + else if (*name == '\0') { /* from previous iteration? */ + *name = *LUA_PATH_SEP; /* restore separator */ + name++; /* skip it */ + } + sep = strchr(name, *LUA_PATH_SEP); /* find next separator */ + if (sep == NULL) /* separator not found? */ + sep = end; /* name goes until the end */ + *sep = '\0'; /* finish file name */ + *path = sep; /* will start next search from here */ + return name; +} + + +/* +** Given a path such as ";blabla.so;blublu.so", pushes the string +** +** no file 'blabla.so' +** no file 'blublu.so' +*/ +static void pusherrornotfound (lua_State *L, const char *path) { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addstring(&b, "no file '"); + luaL_addgsub(&b, path, LUA_PATH_SEP, "'\n\tno file '"); + luaL_addstring(&b, "'"); + luaL_pushresult(&b); } @@ -397,21 +473,25 @@ static const char *searchpath (lua_State *L, const char *name, const char *path, const char *sep, const char *dirsep) { - luaL_Buffer msg; /* to build error message */ - luaL_buffinit(L, &msg); - if (*sep != '\0') /* non-empty separator? */ + luaL_Buffer buff; + char *pathname; /* path with name inserted */ + char *endpathname; /* its end */ + const char *filename; + /* separator is non-empty and appears in 'name'? */ + if (*sep != '\0' && strchr(name, *sep) != NULL) name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */ - while ((path = pushnexttemplate(L, path)) != NULL) { - const char *filename = luaL_gsub(L, lua_tostring(L, -1), - LUA_PATH_MARK, name); - lua_remove(L, -2); /* remove path template */ + luaL_buffinit(L, &buff); + /* add path to the buffer, replacing marks ('?') with the file name */ + luaL_addgsub(&buff, path, LUA_PATH_MARK, name); + luaL_addchar(&buff, '\0'); + pathname = luaL_buffaddr(&buff); /* writable list of file names */ + endpathname = pathname + luaL_bufflen(&buff) - 1; + while ((filename = getnextfilename(&pathname, endpathname)) != NULL) { if (readable(filename)) /* does file exist and is readable? */ - return filename; /* return that file name */ - lua_pushfstring(L, "\n\tno file '%s'", filename); - lua_remove(L, -2); /* remove file name */ - luaL_addvalue(&msg); /* concatenate error msg. entry */ + return lua_pushstring(L, filename); /* save and return name */ } - luaL_pushresult(&msg); /* create error message */ + luaL_pushresult(&buff); /* push path to create error message */ + pusherrornotfound(L, lua_tostring(L, -1)); /* create error message */ return NULL; /* not found */ } @@ -423,9 +503,9 @@ static int ll_searchpath (lua_State *L) { luaL_optstring(L, 4, LUA_DIRSEP)); if (f != NULL) return 1; else { /* error message is on top of the stack */ - lua_pushnil(L); + luaL_pushfail(L); lua_insert(L, -2); - return 2; /* return nil + error message */ + return 2; /* return fail + error message */ } } @@ -436,14 +516,14 @@ static const char *findfile (lua_State *L, const char *name, const char *path; lua_getfield(L, lua_upvalueindex(1), pname); path = lua_tostring(L, -1); - if (path == NULL) + if (l_unlikely(path == NULL)) luaL_error(L, "'package.%s' must be a string", pname); return searchpath(L, name, path, ".", dirsep); } static int checkload (lua_State *L, int stat, const char *filename) { - if (stat) { /* module loaded successfully? */ + if (l_likely(stat)) { /* module loaded successfully? */ lua_pushstring(L, filename); /* will be 2nd argument to module */ return 2; /* return open function and file name */ } @@ -509,7 +589,7 @@ static int searcher_Croot (lua_State *L) { if (stat != ERRFUNC) return checkload(L, 0, filename); /* real error */ else { /* open function not found */ - lua_pushfstring(L, "\n\tno module '%s' in file '%s'", name, filename); + lua_pushfstring(L, "no module '%s' in file '%s'", name, filename); return 1; } } @@ -520,24 +600,32 @@ static int searcher_Croot (lua_State *L) { static int searcher_preload (lua_State *L) { const char *name = luaL_checkstring(L, 1); - lua_getfield(L, LUA_REGISTRYINDEX, "_PRELOAD"); - if (lua_getfield(L, -1, name) == LUA_TNIL) /* not found? */ - lua_pushfstring(L, "\n\tno field package.preload['%s']", name); - return 1; + lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); + if (lua_getfield(L, -1, name) == LUA_TNIL) { /* not found? */ + lua_pushfstring(L, "no field package.preload['%s']", name); + return 1; + } + else { + lua_pushliteral(L, ":preload:"); + return 2; + } } static void findloader (lua_State *L, const char *name) { int i; luaL_Buffer msg; /* to build error message */ - luaL_buffinit(L, &msg); /* push 'package.searchers' to index 3 in the stack */ - if (lua_getfield(L, lua_upvalueindex(1), "searchers") != LUA_TTABLE) + if (l_unlikely(lua_getfield(L, lua_upvalueindex(1), "searchers") + != LUA_TTABLE)) luaL_error(L, "'package.searchers' must be a table"); + luaL_buffinit(L, &msg); /* iterate over available searchers to find a loader */ for (i = 1; ; i++) { - if (lua_rawgeti(L, 3, i) == LUA_TNIL) { /* no more searchers? */ + luaL_addstring(&msg, "\n\t"); /* error-message prefix */ + if (l_unlikely(lua_rawgeti(L, 3, i) == LUA_TNIL)) { /* no more searchers? */ lua_pop(L, 1); /* remove nil */ + luaL_buffsub(&msg, 2); /* remove prefix */ luaL_pushresult(&msg); /* create error message */ luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1)); } @@ -549,164 +637,51 @@ static void findloader (lua_State *L, const char *name) { lua_pop(L, 1); /* remove extra return */ luaL_addvalue(&msg); /* concatenate error message */ } - else + else { /* no error message */ lua_pop(L, 2); /* remove both returns */ + luaL_buffsub(&msg, 2); /* remove prefix */ + } } } static int ll_require (lua_State *L) { const char *name = luaL_checkstring(L, 1); - lua_settop(L, 1); /* _LOADED table will be at index 2 */ - lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); - lua_getfield(L, 2, name); /* _LOADED[name] */ + lua_settop(L, 1); /* LOADED table will be at index 2 */ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, 2, name); /* LOADED[name] */ if (lua_toboolean(L, -1)) /* is it there? */ return 1; /* package is already loaded */ /* else must load package */ lua_pop(L, 1); /* remove 'getfield' result */ findloader(L, name); - lua_pushstring(L, name); /* pass name as argument to module loader */ - lua_insert(L, -2); /* name is 1st argument (before search data) */ + lua_rotate(L, -2, 1); /* function <-> loader data */ + lua_pushvalue(L, 1); /* name is 1st argument to module loader */ + lua_pushvalue(L, -3); /* loader data is 2nd argument */ + /* stack: ...; loader data; loader function; mod. name; loader data */ lua_call(L, 2, 1); /* run loader to load module */ + /* stack: ...; loader data; result from loader */ if (!lua_isnil(L, -1)) /* non-nil return? */ - lua_setfield(L, 2, name); /* _LOADED[name] = returned value */ + lua_setfield(L, 2, name); /* LOADED[name] = returned value */ + else + lua_pop(L, 1); /* pop nil */ if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */ lua_pushboolean(L, 1); /* use true as result */ - lua_pushvalue(L, -1); /* extra copy to be returned */ - lua_setfield(L, 2, name); /* _LOADED[name] = true */ - } - return 1; -} - -/* }====================================================== */ - - - -/* -** {====================================================== -** 'module' function -** ======================================================= -*/ -#if defined(LUA_COMPAT_MODULE) - -/* -** changes the environment variable of calling function -*/ -static void set_env (lua_State *L) { - lua_Debug ar; - if (lua_getstack(L, 1, &ar) == 0 || - lua_getinfo(L, "f", &ar) == 0 || /* get calling function */ - lua_iscfunction(L, -1)) - luaL_error(L, "'module' not called from a Lua function"); - lua_pushvalue(L, -2); /* copy new environment table to top */ - lua_setupvalue(L, -2, 1); - lua_pop(L, 1); /* remove function */ -} - - -static void dooptions (lua_State *L, int n) { - int i; - for (i = 2; i <= n; i++) { - if (lua_isfunction(L, i)) { /* avoid 'calling' extra info. */ - lua_pushvalue(L, i); /* get option (a function) */ - lua_pushvalue(L, -2); /* module */ - lua_call(L, 1, 0); - } + lua_copy(L, -1, -2); /* replace loader result */ + lua_setfield(L, 2, name); /* LOADED[name] = true */ } + lua_rotate(L, -2, 1); /* loader data <-> module result */ + return 2; /* return module result and loader data */ } - -static void modinit (lua_State *L, const char *modname) { - const char *dot; - lua_pushvalue(L, -1); - lua_setfield(L, -2, "_M"); /* module._M = module */ - lua_pushstring(L, modname); - lua_setfield(L, -2, "_NAME"); - dot = strrchr(modname, '.'); /* look for last dot in module name */ - if (dot == NULL) dot = modname; - else dot++; - /* set _PACKAGE as package name (full module name minus last part) */ - lua_pushlstring(L, modname, dot - modname); - lua_setfield(L, -2, "_PACKAGE"); -} - - -static int ll_module (lua_State *L) { - const char *modname = luaL_checkstring(L, 1); - int lastarg = lua_gettop(L); /* last parameter */ - luaL_pushmodule(L, modname, 1); /* get/create module table */ - /* check whether table already has a _NAME field */ - if (lua_getfield(L, -1, "_NAME") != LUA_TNIL) - lua_pop(L, 1); /* table is an initialized module */ - else { /* no; initialize it */ - lua_pop(L, 1); - modinit(L, modname); - } - lua_pushvalue(L, -1); - set_env(L); - dooptions(L, lastarg); - return 1; -} - - -static int ll_seeall (lua_State *L) { - luaL_checktype(L, 1, LUA_TTABLE); - if (!lua_getmetatable(L, 1)) { - lua_createtable(L, 0, 1); /* create new metatable */ - lua_pushvalue(L, -1); - lua_setmetatable(L, 1); - } - lua_pushglobaltable(L); - lua_setfield(L, -2, "__index"); /* mt.__index = _G */ - return 0; -} - -#endif /* }====================================================== */ -/* auxiliary mark (for internal use) */ -#define AUXMARK "\1" - - -/* -** return registry.LUA_NOENV as a boolean -*/ -static int noenv (lua_State *L) { - int b; - lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); - b = lua_toboolean(L, -1); - lua_pop(L, 1); /* remove value */ - return b; -} - - -static void setpath (lua_State *L, const char *fieldname, const char *envname1, - const char *envname2, const char *def) { - const char *path = getenv(envname1); - if (path == NULL) /* no environment variable? */ - path = getenv(envname2); /* try alternative name */ - if (path == NULL || noenv(L)) /* no environment variable? */ - lua_pushstring(L, def); /* use default */ - else { - /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */ - path = luaL_gsub(L, path, LUA_PATH_SEP LUA_PATH_SEP, - LUA_PATH_SEP AUXMARK LUA_PATH_SEP); - luaL_gsub(L, path, AUXMARK, def); - lua_remove(L, -2); - } - setprogdir(L); - lua_setfield(L, -2, fieldname); -} - static const luaL_Reg pk_funcs[] = { {"loadlib", ll_loadlib}, {"searchpath", ll_searchpath}, -#if defined(LUA_COMPAT_MODULE) - {"seeall", ll_seeall}, -#endif /* placeholders */ {"preload", NULL}, {"cpath", NULL}, @@ -718,30 +693,28 @@ static const luaL_Reg pk_funcs[] = { static const luaL_Reg ll_funcs[] = { -#if defined(LUA_COMPAT_MODULE) - {"module", ll_module}, -#endif {"require", ll_require}, {NULL, NULL} }; static void createsearcherstable (lua_State *L) { - static const lua_CFunction searchers[] = - {searcher_preload, searcher_Lua, searcher_C, searcher_Croot, NULL}; + static const lua_CFunction searchers[] = { + searcher_preload, + searcher_Lua, + searcher_C, + searcher_Croot, + NULL + }; int i; /* create 'searchers' table */ lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0); - /* fill it with pre-defined searchers */ + /* fill it with predefined searchers */ for (i=0; searchers[i] != NULL; i++) { lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */ lua_pushcclosure(L, searchers[i], 1); lua_rawseti(L, -2, i+1); } -#if defined(LUA_COMPAT_LOADERS) - lua_pushvalue(L, -1); /* make a copy of 'searchers' table */ - lua_setfield(L, -3, "loaders"); /* put it in field 'loaders' */ -#endif lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ } @@ -751,12 +724,11 @@ static void createsearcherstable (lua_State *L) { ** setting a finalizer to close all libraries when closing state. */ static void createclibstable (lua_State *L) { - lua_newtable(L); /* create CLIBS table */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); /* create CLIBS table */ lua_createtable(L, 0, 1); /* create metatable for CLIBS */ lua_pushcfunction(L, gctm); lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */ lua_setmetatable(L, -2); - lua_rawsetp(L, LUA_REGISTRYINDEX, &CLIBS); /* set CLIBS table in registry */ } @@ -764,19 +736,18 @@ LUAMOD_API int luaopen_package (lua_State *L) { createclibstable(L); luaL_newlib(L, pk_funcs); /* create 'package' table */ createsearcherstable(L); - /* set field 'path' */ - setpath(L, "path", LUA_PATHVARVERSION, LUA_PATH_VAR, LUA_PATH_DEFAULT); - /* set field 'cpath' */ - setpath(L, "cpath", LUA_CPATHVARVERSION, LUA_CPATH_VAR, LUA_CPATH_DEFAULT); + /* set paths */ + setpath(L, "path", LUA_PATH_VAR, LUA_PATH_DEFAULT); + setpath(L, "cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT); /* store config information */ lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); lua_setfield(L, -2, "config"); /* set field 'loaded' */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); lua_setfield(L, -2, "loaded"); /* set field 'preload' */ - luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD"); + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); lua_setfield(L, -2, "preload"); lua_pushglobaltable(L); lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib */ diff --git a/libs/lua/lobject.c b/libs/lua/lobject.c index 6c53b981..9cfa5227 100644 --- a/libs/lua/lobject.c +++ b/libs/lua/lobject.c @@ -1,5 +1,5 @@ /* -** $Id: lobject.c,v 2.104 2015/04/11 18:30:08 roberto Exp $ +** $Id: lobject.c $ ** Some generic functions over Lua objects ** See Copyright Notice in lua.h */ @@ -29,38 +29,6 @@ #include "lvm.h" - -LUAI_DDEF const TValue luaO_nilobject_ = {NILCONSTANT}; - - -/* -** converts an integer to a "floating point byte", represented as -** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if -** eeeee != 0 and (xxx) otherwise. -*/ -int luaO_int2fb (unsigned int x) { - int e = 0; /* exponent */ - if (x < 8) return x; - while (x >= (8 << 4)) { /* coarse steps */ - x = (x + 0xf) >> 4; /* x = ceil(x / 16) */ - e += 4; - } - while (x >= (8 << 1)) { /* fine steps */ - x = (x + 1) >> 1; /* x = ceil(x / 2) */ - e++; - } - return ((e+1) << 3) | (cast_int(x) - 8); -} - - -/* converts back */ -int luaO_fb2int (int x) { - int e = (x >> 3) & 0x1f; - if (e == 0) return x; - else return ((x & 7) + 8) << (e - 1); -} - - /* ** Computes ceil(log2(x)) */ @@ -89,12 +57,12 @@ static lua_Integer intarith (lua_State *L, int op, lua_Integer v1, case LUA_OPSUB:return intop(-, v1, v2); case LUA_OPMUL:return intop(*, v1, v2); case LUA_OPMOD: return luaV_mod(L, v1, v2); - case LUA_OPIDIV: return luaV_div(L, v1, v2); + case LUA_OPIDIV: return luaV_idiv(L, v1, v2); case LUA_OPBAND: return intop(&, v1, v2); case LUA_OPBOR: return intop(|, v1, v2); case LUA_OPBXOR: return intop(^, v1, v2); case LUA_OPSHL: return luaV_shiftl(v1, v2); - case LUA_OPSHR: return luaV_shiftl(v1, -v2); + case LUA_OPSHR: return luaV_shiftr(v1, v2); case LUA_OPUNM: return intop(-, 0, v1); case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1); default: lua_assert(0); return 0; @@ -112,53 +80,55 @@ static lua_Number numarith (lua_State *L, int op, lua_Number v1, case LUA_OPPOW: return luai_numpow(L, v1, v2); case LUA_OPIDIV: return luai_numidiv(L, v1, v2); case LUA_OPUNM: return luai_numunm(L, v1); - case LUA_OPMOD: { - lua_Number m; - luai_nummod(L, v1, v2, m); - return m; - } + case LUA_OPMOD: return luaV_modf(L, v1, v2); default: lua_assert(0); return 0; } } -void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2, - TValue *res) { +int luaO_rawarith (lua_State *L, int op, const TValue *p1, const TValue *p2, + TValue *res) { switch (op) { case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: { /* operate only on integers */ lua_Integer i1; lua_Integer i2; - if (tointeger(p1, &i1) && tointeger(p2, &i2)) { + if (tointegerns(p1, &i1) && tointegerns(p2, &i2)) { setivalue(res, intarith(L, op, i1, i2)); - return; + return 1; } - else break; /* go to the end */ + else return 0; /* fail */ } case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */ lua_Number n1; lua_Number n2; - if (tonumber(p1, &n1) && tonumber(p2, &n2)) { + if (tonumberns(p1, n1) && tonumberns(p2, n2)) { setfltvalue(res, numarith(L, op, n1, n2)); - return; + return 1; } - else break; /* go to the end */ + else return 0; /* fail */ } default: { /* other operations */ lua_Number n1; lua_Number n2; if (ttisinteger(p1) && ttisinteger(p2)) { setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2))); - return; + return 1; } - else if (tonumber(p1, &n1) && tonumber(p2, &n2)) { + else if (tonumberns(p1, n1) && tonumberns(p2, n2)) { setfltvalue(res, numarith(L, op, n1, n2)); - return; + return 1; } - else break; /* go to the end */ + else return 0; /* fail */ } } - /* could not perform raw operation; try metamethod */ - lua_assert(L != NULL); /* should not fail when folding (compile time) */ - luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD)); +} + + +void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2, + StkId res) { + if (!luaO_rawarith(L, op, p1, p2, s2v(res))) { + /* could not perform raw operation; try metamethod */ + luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD)); + } } @@ -189,22 +159,22 @@ static int isneg (const char **s) { #define MAXSIGDIG 30 /* -** convert an hexadecimal numeric string to a number, following +** convert a hexadecimal numeric string to a number, following ** C99 specification for 'strtod' */ static lua_Number lua_strx2number (const char *s, char **endptr) { int dot = lua_getlocaledecpoint(); - lua_Number r = 0.0; /* result (accumulator) */ + lua_Number r = l_mathop(0.0); /* result (accumulator) */ int sigdig = 0; /* number of significant digits */ int nosigdig = 0; /* number of non-significant digits */ int e = 0; /* exponent correction */ int neg; /* 1 if number is negative */ int hasdot = 0; /* true after seen a dot */ - *endptr = cast(char *, s); /* nothing is valid yet */ + *endptr = cast_charp(s); /* nothing is valid yet */ while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ - neg = isneg(&s); /* check signal */ + neg = isneg(&s); /* check sign */ if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */ - return 0.0; /* invalid format (no '0x') */ + return l_mathop(0.0); /* invalid format (no '0x') */ for (s += 2; ; s++) { /* skip '0x' and read numeral */ if (*s == dot) { if (hasdot) break; /* second dot? stop loop */ @@ -214,28 +184,28 @@ static lua_Number lua_strx2number (const char *s, char **endptr) { if (sigdig == 0 && *s == '0') /* non-significant digit (zero)? */ nosigdig++; else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */ - r = (r * cast_num(16.0)) + luaO_hexavalue(*s); + r = (r * l_mathop(16.0)) + luaO_hexavalue(*s); else e++; /* too many digits; ignore, but still count for exponent */ if (hasdot) e--; /* decimal digit? correct exponent */ } else break; /* neither a dot nor a digit */ } if (nosigdig + sigdig == 0) /* no digits? */ - return 0.0; /* invalid format */ - *endptr = cast(char *, s); /* valid up to here */ + return l_mathop(0.0); /* invalid format */ + *endptr = cast_charp(s); /* valid up to here */ e *= 4; /* each digit multiplies/divides value by 2^4 */ if (*s == 'p' || *s == 'P') { /* exponent part? */ int exp1 = 0; /* exponent value */ - int neg1; /* exponent signal */ + int neg1; /* exponent sign */ s++; /* skip 'p' */ - neg1 = isneg(&s); /* signal */ + neg1 = isneg(&s); /* sign */ if (!lisdigit(cast_uchar(*s))) - return 0.0; /* invalid; must have at least one digit */ + return l_mathop(0.0); /* invalid; must have at least one digit */ while (lisdigit(cast_uchar(*s))) /* read exponent */ exp1 = exp1 * 10 + *(s++) - '0'; if (neg1) exp1 = -exp1; e += exp1; - *endptr = cast(char *, s); /* valid up to here */ + *endptr = cast_charp(s); /* valid up to here */ } if (neg) r = -r; return l_mathop(ldexp)(r, e); @@ -245,20 +215,64 @@ static lua_Number lua_strx2number (const char *s, char **endptr) { /* }====================================================== */ -static const char *l_str2d (const char *s, lua_Number *result) { +/* maximum length of a numeral to be converted to a number */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + +/* +** Convert string 's' to a Lua number (put in 'result'). Return NULL on +** fail or the address of the ending '\0' on success. ('mode' == 'x') +** means a hexadecimal numeral. +*/ +static const char *l_str2dloc (const char *s, lua_Number *result, int mode) { char *endptr; - if (strpbrk(s, "nN")) /* reject 'inf' and 'nan' */ + *result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */ + : lua_str2number(s, &endptr); + if (endptr == s) return NULL; /* nothing recognized? */ + while (lisspace(cast_uchar(*endptr))) endptr++; /* skip trailing spaces */ + return (*endptr == '\0') ? endptr : NULL; /* OK iff no trailing chars */ +} + + +/* +** Convert string 's' to a Lua number (put in 'result') handling the +** current locale. +** This function accepts both the current locale or a dot as the radix +** mark. If the conversion fails, it may mean number has a dot but +** locale accepts something else. In that case, the code copies 's' +** to a buffer (because 's' is read-only), changes the dot to the +** current locale radix mark, and tries to convert again. +** The variable 'mode' checks for special characters in the string: +** - 'n' means 'inf' or 'nan' (which should be rejected) +** - 'x' means a hexadecimal numeral +** - '.' just optimizes the search for the common case (no special chars) +*/ +static const char *l_str2d (const char *s, lua_Number *result) { + const char *endptr; + const char *pmode = strpbrk(s, ".xXnN"); /* look for special chars */ + int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0; + if (mode == 'n') /* reject 'inf' and 'nan' */ return NULL; - else if (strpbrk(s, "xX")) /* hex? */ - *result = lua_strx2number(s, &endptr); - else - *result = lua_str2number(s, &endptr); - if (endptr == s) return NULL; /* nothing recognized */ - while (lisspace(cast_uchar(*endptr))) endptr++; - return (*endptr == '\0' ? endptr : NULL); /* OK if no trailing characters */ + endptr = l_str2dloc(s, result, mode); /* try to convert */ + if (endptr == NULL) { /* failed? may be a different locale */ + char buff[L_MAXLENNUM + 1]; + const char *pdot = strchr(s, '.'); + if (pdot == NULL || strlen(s) > L_MAXLENNUM) + return NULL; /* string too long or no dot; fail */ + strcpy(buff, s); /* copy string to buffer */ + buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */ + endptr = l_str2dloc(buff, result, mode); /* try again */ + if (endptr != NULL) + endptr = s + (endptr - buff); /* make relative to 's' */ + } + return endptr; } +#define MAXBY10 cast(lua_Unsigned, LUA_MAXINTEGER / 10) +#define MAXLASTD cast_int(LUA_MAXINTEGER % 10) + static const char *l_str2int (const char *s, lua_Integer *result) { lua_Unsigned a = 0; int empty = 1; @@ -275,7 +289,10 @@ static const char *l_str2int (const char *s, lua_Integer *result) { } else { /* decimal */ for (; lisdigit(cast_uchar(*s)); s++) { - a = a * 10 + *s - '0'; + int d = *s - '0'; + if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ + return NULL; /* do not accept it (as integer) */ + a = a * 10 + d; empty = 0; } } @@ -305,106 +322,214 @@ size_t luaO_str2num (const char *s, TValue *o) { int luaO_utf8esc (char *buff, unsigned long x) { int n = 1; /* number of bytes put in buffer (backwards) */ - lua_assert(x <= 0x10FFFF); + lua_assert(x <= 0x7FFFFFFFu); if (x < 0x80) /* ascii? */ - buff[UTF8BUFFSZ - 1] = cast(char, x); + buff[UTF8BUFFSZ - 1] = cast_char(x); else { /* need continuation bytes */ unsigned int mfb = 0x3f; /* maximum that fits in first byte */ do { /* add continuation bytes */ - buff[UTF8BUFFSZ - (n++)] = cast(char, 0x80 | (x & 0x3f)); + buff[UTF8BUFFSZ - (n++)] = cast_char(0x80 | (x & 0x3f)); x >>= 6; /* remove added bits */ mfb >>= 1; /* now there is one less bit available in first byte */ } while (x > mfb); /* still needs continuation byte? */ - buff[UTF8BUFFSZ - n] = cast(char, (~mfb << 1) | x); /* add first byte */ + buff[UTF8BUFFSZ - n] = cast_char((~mfb << 1) | x); /* add first byte */ } return n; } -/* maximum length of the conversion of a number to a string */ -#define MAXNUMBER2STR 50 +/* +** Maximum length of the conversion of a number to a string. Must be +** enough to accommodate both LUA_INTEGER_FMT and LUA_NUMBER_FMT. +** (For a long long int, this is 19 digits plus a sign and a final '\0', +** adding to 21. For a long double, it can go to a sign, 33 digits, +** the dot, an exponent letter, an exponent sign, 5 exponent digits, +** and a final '\0', adding to 43.) +*/ +#define MAXNUMBER2STR 44 /* -** Convert a number object to a string +** Convert a number object to a string, adding it to a buffer */ -void luaO_tostring (lua_State *L, StkId obj) { - char buff[MAXNUMBER2STR]; - size_t len; +static int tostringbuff (TValue *obj, char *buff) { + int len; lua_assert(ttisnumber(obj)); if (ttisinteger(obj)) - len = lua_integer2str(buff, ivalue(obj)); + len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj)); else { - len = lua_number2str(buff, fltvalue(obj)); -#if !defined(LUA_COMPAT_FLOATSTRING) + len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj)); if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */ buff[len++] = lua_getlocaledecpoint(); buff[len++] = '0'; /* adds '.0' to result */ } -#endif } - setsvalue2s(L, obj, luaS_newlstr(L, buff, len)); + return len; +} + + +/* +** Convert a number object to a Lua string, replacing the value at 'obj' +*/ +void luaO_tostring (lua_State *L, TValue *obj) { + char buff[MAXNUMBER2STR]; + int len = tostringbuff(obj, buff); + setsvalue(L, obj, luaS_newlstr(L, buff, len)); +} + + + + +/* +** {================================================================== +** 'luaO_pushvfstring' +** =================================================================== +*/ + +/* +** Size for buffer space used by 'luaO_pushvfstring'. It should be +** (LUA_IDSIZE + MAXNUMBER2STR) + a minimal space for basic messages, +** so that 'luaG_addinfo' can work directly on the buffer. +*/ +#define BUFVFS (LUA_IDSIZE + MAXNUMBER2STR + 95) + +/* buffer used by 'luaO_pushvfstring' */ +typedef struct BuffFS { + lua_State *L; + int pushed; /* true if there is a part of the result on the stack */ + int blen; /* length of partial string in 'space' */ + char space[BUFVFS]; /* holds last part of the result */ +} BuffFS; + + +/* +** Push given string to the stack, as part of the result, and +** join it to previous partial result if there is one. +** It may call 'luaV_concat' while using one slot from EXTRA_STACK. +** This call cannot invoke metamethods, as both operands must be +** strings. It can, however, raise an error if the result is too +** long. In that case, 'luaV_concat' frees the extra slot before +** raising the error. +*/ +static void pushstr (BuffFS *buff, const char *str, size_t lstr) { + lua_State *L = buff->L; + setsvalue2s(L, L->top.p, luaS_newlstr(L, str, lstr)); + L->top.p++; /* may use one slot from EXTRA_STACK */ + if (!buff->pushed) /* no previous string on the stack? */ + buff->pushed = 1; /* now there is one */ + else /* join previous string with new one */ + luaV_concat(L, 2); +} + + +/* +** empty the buffer space into the stack +*/ +static void clearbuff (BuffFS *buff) { + pushstr(buff, buff->space, buff->blen); /* push buffer contents */ + buff->blen = 0; /* space now is empty */ +} + + +/* +** Get a space of size 'sz' in the buffer. If buffer has not enough +** space, empty it. 'sz' must fit in an empty buffer. +*/ +static char *getbuff (BuffFS *buff, int sz) { + lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS); + if (sz > BUFVFS - buff->blen) /* not enough space? */ + clearbuff(buff); + return buff->space + buff->blen; +} + + +#define addsize(b,sz) ((b)->blen += (sz)) + + +/* +** Add 'str' to the buffer. If string is larger than the buffer space, +** push the string directly to the stack. +*/ +static void addstr2buff (BuffFS *buff, const char *str, size_t slen) { + if (slen <= BUFVFS) { /* does string fit into buffer? */ + char *bf = getbuff(buff, cast_int(slen)); + memcpy(bf, str, slen); /* add string to buffer */ + addsize(buff, cast_int(slen)); + } + else { /* string larger than buffer */ + clearbuff(buff); /* string comes after buffer's content */ + pushstr(buff, str, slen); /* push string */ + } } -static void pushstr (lua_State *L, const char *str, size_t l) { - setsvalue2s(L, L->top++, luaS_newlstr(L, str, l)); +/* +** Add a numeral to the buffer. +*/ +static void addnum2buff (BuffFS *buff, TValue *num) { + char *numbuff = getbuff(buff, MAXNUMBER2STR); + int len = tostringbuff(num, numbuff); /* format number into 'numbuff' */ + addsize(buff, len); } -/* this function handles only '%d', '%c', '%f', '%p', and '%s' - conventional formats, plus Lua-specific '%I' and '%U' */ +/* +** this function handles only '%d', '%c', '%f', '%p', '%s', and '%%' + conventional formats, plus Lua-specific '%I' and '%U' +*/ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { - int n = 0; - for (;;) { - const char *e = strchr(fmt, '%'); - if (e == NULL) break; - luaD_checkstack(L, 2); /* fmt + item */ - pushstr(L, fmt, e - fmt); - switch (*(e+1)) { - case 's': { + BuffFS buff; /* holds last part of the result */ + const char *e; /* points to next '%' */ + buff.pushed = buff.blen = 0; + buff.L = L; + while ((e = strchr(fmt, '%')) != NULL) { + addstr2buff(&buff, fmt, e - fmt); /* add 'fmt' up to '%' */ + switch (*(e + 1)) { /* conversion specifier */ + case 's': { /* zero-terminated string */ const char *s = va_arg(argp, char *); if (s == NULL) s = "(null)"; - pushstr(L, s, strlen(s)); + addstr2buff(&buff, s, strlen(s)); break; } - case 'c': { - char buff = cast(char, va_arg(argp, int)); - if (lisprint(cast_uchar(buff))) - pushstr(L, &buff, 1); - else /* non-printable character; print its code */ - luaO_pushfstring(L, "<\\%d>", cast_uchar(buff)); + case 'c': { /* an 'int' as a character */ + char c = cast_uchar(va_arg(argp, int)); + addstr2buff(&buff, &c, sizeof(char)); break; } - case 'd': { - setivalue(L->top++, va_arg(argp, int)); - luaO_tostring(L, L->top - 1); + case 'd': { /* an 'int' */ + TValue num; + setivalue(&num, va_arg(argp, int)); + addnum2buff(&buff, &num); break; } - case 'I': { - setivalue(L->top++, cast(lua_Integer, va_arg(argp, l_uacInt))); - luaO_tostring(L, L->top - 1); + case 'I': { /* a 'lua_Integer' */ + TValue num; + setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt))); + addnum2buff(&buff, &num); break; } - case 'f': { - setfltvalue(L->top++, cast_num(va_arg(argp, l_uacNumber))); - luaO_tostring(L, L->top - 1); + case 'f': { /* a 'lua_Number' */ + TValue num; + setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber))); + addnum2buff(&buff, &num); break; } - case 'p': { - char buff[4*sizeof(void *) + 8]; /* should be enough space for a '%p' */ - int l = sprintf(buff, "%p", va_arg(argp, void *)); - pushstr(L, buff, l); + case 'p': { /* a pointer */ + const int sz = 3 * sizeof(void*) + 8; /* enough space for '%p' */ + char *bf = getbuff(&buff, sz); + void *p = va_arg(argp, void *); + int len = lua_pointer2str(bf, sz, p); + addsize(&buff, len); break; } - case 'U': { - char buff[UTF8BUFFSZ]; - int l = luaO_utf8esc(buff, cast(long, va_arg(argp, long))); - pushstr(L, buff + UTF8BUFFSZ - l, l); + case 'U': { /* a 'long' as a UTF-8 sequence */ + char bf[UTF8BUFFSZ]; + int len = luaO_utf8esc(bf, va_arg(argp, long)); + addstr2buff(&buff, bf + UTF8BUFFSZ - len, len); break; } case '%': { - pushstr(L, "%", 1); + addstr2buff(&buff, "%", 1); break; } default: { @@ -412,13 +537,12 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { *(e + 1)); } } - n += 2; - fmt = e+2; + fmt = e + 2; /* skip '%' and the specifier */ } - luaD_checkstack(L, 1); - pushstr(L, fmt, strlen(fmt)); - if (n > 0) luaV_concat(L, n + 1); - return svalue(L->top - 1); + addstr2buff(&buff, fmt, strlen(fmt)); /* rest of 'fmt' */ + clearbuff(&buff); /* empty buffer into the stack */ + lua_assert(buff.pushed == 1); + return getstr(tsvalue(s2v(L->top.p - 1))); } @@ -431,9 +555,8 @@ const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { return msg; } +/* }================================================================== */ -/* number of chars of a literal string without the ending \0 */ -#define LL(x) (sizeof(x)/sizeof(char) - 1) #define RETS "..." #define PRE "[string \"" @@ -441,36 +564,36 @@ const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { #define addstr(a,b,l) ( memcpy(a,b,(l) * sizeof(char)), a += (l) ) -void luaO_chunkid (char *out, const char *source, size_t bufflen) { - size_t l = strlen(source); +void luaO_chunkid (char *out, const char *source, size_t srclen) { + size_t bufflen = LUA_IDSIZE; /* free space in buffer */ if (*source == '=') { /* 'literal' source */ - if (l <= bufflen) /* small enough? */ - memcpy(out, source + 1, l * sizeof(char)); + if (srclen <= bufflen) /* small enough? */ + memcpy(out, source + 1, srclen * sizeof(char)); else { /* truncate it */ addstr(out, source + 1, bufflen - 1); *out = '\0'; } } else if (*source == '@') { /* file name */ - if (l <= bufflen) /* small enough? */ - memcpy(out, source + 1, l * sizeof(char)); + if (srclen <= bufflen) /* small enough? */ + memcpy(out, source + 1, srclen * sizeof(char)); else { /* add '...' before rest of name */ addstr(out, RETS, LL(RETS)); bufflen -= LL(RETS); - memcpy(out, source + 1 + l - bufflen, bufflen * sizeof(char)); + memcpy(out, source + 1 + srclen - bufflen, bufflen * sizeof(char)); } } else { /* string; format as [string "source"] */ const char *nl = strchr(source, '\n'); /* find first new line (if any) */ addstr(out, PRE, LL(PRE)); /* add prefix */ bufflen -= LL(PRE RETS POS) + 1; /* save space for prefix+suffix+'\0' */ - if (l < bufflen && nl == NULL) { /* small one-line source? */ - addstr(out, source, l); /* keep it */ + if (srclen < bufflen && nl == NULL) { /* small one-line source? */ + addstr(out, source, srclen); /* keep it */ } else { - if (nl != NULL) l = nl - source; /* stop at first newline */ - if (l > bufflen) l = bufflen; - addstr(out, source, l); + if (nl != NULL) srclen = nl - source; /* stop at first newline */ + if (srclen > bufflen) srclen = bufflen; + addstr(out, source, srclen); addstr(out, RETS, LL(RETS)); } memcpy(out, POS, (LL(POS) + 1) * sizeof(char)); diff --git a/libs/lua/lobject.h b/libs/lua/lobject.h index 9230b7a9..980e42f8 100644 --- a/libs/lua/lobject.h +++ b/libs/lua/lobject.h @@ -1,5 +1,5 @@ /* -** $Id: lobject.h,v 2.111 2015/06/09 14:21:42 roberto Exp $ +** $Id: lobject.h $ ** Type definitions for Lua objects ** See Copyright Notice in lua.h */ @@ -17,371 +17,494 @@ /* -** Extra tags for non-values +** Extra types for collectable non-values */ -#define LUA_TPROTO LUA_NUMTAGS -#define LUA_TDEADKEY (LUA_NUMTAGS+1) +#define LUA_TUPVAL LUA_NUMTYPES /* upvalues */ +#define LUA_TPROTO (LUA_NUMTYPES+1) /* function prototypes */ +#define LUA_TDEADKEY (LUA_NUMTYPES+2) /* removed keys in tables */ + + /* -** number of all possible tags (including LUA_TNONE but excluding DEADKEY) +** number of all possible types (including LUA_TNONE but excluding DEADKEY) */ -#define LUA_TOTALTAGS (LUA_TPROTO + 2) +#define LUA_TOTALTYPES (LUA_TPROTO + 2) /* ** tags for Tagged Values have the following use of bits: -** bits 0-3: actual tag (a LUA_T* value) +** bits 0-3: actual tag (a LUA_T* constant) ** bits 4-5: variant bits ** bit 6: whether value is collectable */ +/* add variant bits to a type */ +#define makevariant(t,v) ((t) | ((v) << 4)) + + /* -** LUA_TFUNCTION variants: -** 0 - Lua function -** 1 - light C function -** 2 - regular C function (closure) +** Union of all Lua values */ +typedef union Value { + struct GCObject *gc; /* collectable objects */ + void *p; /* light userdata */ + lua_CFunction f; /* light C functions */ + lua_Integer i; /* integer numbers */ + lua_Number n; /* float numbers */ + /* not used, but may avoid warnings for uninitialized value */ + lu_byte ub; +} Value; -/* Variant tags for functions */ -#define LUA_TLCL (LUA_TFUNCTION | (0 << 4)) /* Lua closure */ -#define LUA_TLCF (LUA_TFUNCTION | (1 << 4)) /* light C function */ -#define LUA_TCCL (LUA_TFUNCTION | (2 << 4)) /* C closure */ +/* +** Tagged Values. This is the basic representation of values in Lua: +** an actual value plus a tag with its type. +*/ -/* Variant tags for strings */ -#define LUA_TSHRSTR (LUA_TSTRING | (0 << 4)) /* short strings */ -#define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) /* long strings */ +#define TValuefields Value value_; lu_byte tt_ +typedef struct TValue { + TValuefields; +} TValue; -/* Variant tags for numbers */ -#define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) /* float numbers */ -#define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) /* integer numbers */ +#define val_(o) ((o)->value_) +#define valraw(o) (val_(o)) -/* Bit mark for collectable types */ -#define BIT_ISCOLLECTABLE (1 << 6) -/* mark a tag as collectable */ -#define ctb(t) ((t) | BIT_ISCOLLECTABLE) +/* raw type tag of a TValue */ +#define rawtt(o) ((o)->tt_) + +/* tag with no variants (bits 0-3) */ +#define novariant(t) ((t) & 0x0F) + +/* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */ +#define withvariant(t) ((t) & 0x3F) +#define ttypetag(o) withvariant(rawtt(o)) + +/* type of a TValue */ +#define ttype(o) (novariant(rawtt(o))) +/* Macros to test type */ +#define checktag(o,t) (rawtt(o) == (t)) +#define checktype(o,t) (ttype(o) == (t)) + + +/* Macros for internal tests */ + +/* collectable object has the same tag as the original value */ +#define righttt(obj) (ttypetag(obj) == gcvalue(obj)->tt) + /* -** Common type for all collectable objects +** Any value being manipulated by the program either is non +** collectable, or the collectable object has the right tag +** and it is not dead. The option 'L == NULL' allows other +** macros using this one to be used where L is not available. */ -typedef struct GCObject GCObject; +#define checkliveness(L,obj) \ + ((void)L, lua_longassert(!iscollectable(obj) || \ + (righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj)))))) + + +/* Macros to set values */ + +/* set a value's tag */ +#define settt_(o,t) ((o)->tt_=(t)) +/* main macro to copy values (from 'obj2' to 'obj1') */ +#define setobj(L,obj1,obj2) \ + { TValue *io1=(obj1); const TValue *io2=(obj2); \ + io1->value_ = io2->value_; settt_(io1, io2->tt_); \ + checkliveness(L,io1); lua_assert(!isnonstrictnil(io1)); } + /* -** Common Header for all collectable objects (in macro form, to be -** included in other objects) +** Different types of assignments, according to source and destination. +** (They are mostly equal now, but may be different in the future.) */ -#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked + +/* from stack to stack */ +#define setobjs2s(L,o1,o2) setobj(L,s2v(o1),s2v(o2)) +/* to stack (not from same stack) */ +#define setobj2s(L,o1,o2) setobj(L,s2v(o1),o2) +/* from table to same table */ +#define setobjt2t setobj +/* to new object */ +#define setobj2n setobj +/* to table */ +#define setobj2t setobj /* -** Common type has only the common header +** Entries in a Lua stack. Field 'tbclist' forms a list of all +** to-be-closed variables active in this stack. Dummy entries are +** used when the distance between two tbc variables does not fit +** in an unsigned short. They are represented by delta==0, and +** their real delta is always the maximum value that fits in +** that field. */ -struct GCObject { - CommonHeader; -}; +typedef union StackValue { + TValue val; + struct { + TValuefields; + unsigned short delta; + } tbclist; +} StackValue; + +/* index to stack elements */ +typedef StackValue *StkId; /* -** Union of all Lua values +** When reallocating the stack, change all pointers to the stack into +** proper offsets. */ -typedef union Value Value; +typedef union { + StkId p; /* actual pointer */ + ptrdiff_t offset; /* used while the stack is being reallocated */ +} StkIdRel; + +/* convert a 'StackValue' to a 'TValue' */ +#define s2v(o) (&(o)->val) /* -** Tagged Values. This is the basic representation of values in Lua, -** an actual value plus a tag with its type. +** {================================================================== +** Nil +** =================================================================== */ -#define TValuefields Value value_; int tt_ +/* Standard nil */ +#define LUA_VNIL makevariant(LUA_TNIL, 0) -typedef struct lua_TValue TValue; +/* Empty slot (which might be different from a slot containing nil) */ +#define LUA_VEMPTY makevariant(LUA_TNIL, 1) +/* Value returned for a key not found in a table (absent key) */ +#define LUA_VABSTKEY makevariant(LUA_TNIL, 2) -/* macro defining a nil value */ -#define NILCONSTANT {NULL}, LUA_TNIL +/* macro to test for (any kind of) nil */ +#define ttisnil(v) checktype((v), LUA_TNIL) -#define val_(o) ((o)->value_) +/* macro to test for a standard nil */ +#define ttisstrictnil(o) checktag((o), LUA_VNIL) -/* raw type tag of a TValue */ -#define rttype(o) ((o)->tt_) -/* tag with no variants (bits 0-3) */ -#define novariant(x) ((x) & 0x0F) +#define setnilvalue(obj) settt_(obj, LUA_VNIL) -/* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */ -#define ttype(o) (rttype(o) & 0x3F) -/* type tag of a TValue with no variants (bits 0-3) */ -#define ttnov(o) (novariant(rttype(o))) +#define isabstkey(v) checktag((v), LUA_VABSTKEY) -/* Macros to test type */ -#define checktag(o,t) (rttype(o) == (t)) -#define checktype(o,t) (ttnov(o) == (t)) -#define ttisnumber(o) checktype((o), LUA_TNUMBER) -#define ttisfloat(o) checktag((o), LUA_TNUMFLT) -#define ttisinteger(o) checktag((o), LUA_TNUMINT) -#define ttisnil(o) checktag((o), LUA_TNIL) -#define ttisboolean(o) checktag((o), LUA_TBOOLEAN) -#define ttislightuserdata(o) checktag((o), LUA_TLIGHTUSERDATA) -#define ttisstring(o) checktype((o), LUA_TSTRING) -#define ttisshrstring(o) checktag((o), ctb(LUA_TSHRSTR)) -#define ttislngstring(o) checktag((o), ctb(LUA_TLNGSTR)) -#define ttistable(o) checktag((o), ctb(LUA_TTABLE)) -#define ttisfunction(o) checktype(o, LUA_TFUNCTION) -#define ttisclosure(o) ((rttype(o) & 0x1F) == LUA_TFUNCTION) -#define ttisCclosure(o) checktag((o), ctb(LUA_TCCL)) -#define ttisLclosure(o) checktag((o), ctb(LUA_TLCL)) -#define ttislcf(o) checktag((o), LUA_TLCF) -#define ttisfulluserdata(o) checktag((o), ctb(LUA_TUSERDATA)) -#define ttisthread(o) checktag((o), ctb(LUA_TTHREAD)) -#define ttisdeadkey(o) checktag((o), LUA_TDEADKEY) +/* +** macro to detect non-standard nils (used only in assertions) +*/ +#define isnonstrictnil(v) (ttisnil(v) && !ttisstrictnil(v)) -/* Macros to access values */ -#define ivalue(o) check_exp(ttisinteger(o), val_(o).i) -#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n) -#define nvalue(o) check_exp(ttisnumber(o), \ - (ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o))) -#define gcvalue(o) check_exp(iscollectable(o), val_(o).gc) -#define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p) -#define tsvalue(o) check_exp(ttisstring(o), gco2ts(val_(o).gc)) -#define uvalue(o) check_exp(ttisfulluserdata(o), gco2u(val_(o).gc)) -#define clvalue(o) check_exp(ttisclosure(o), gco2cl(val_(o).gc)) -#define clLvalue(o) check_exp(ttisLclosure(o), gco2lcl(val_(o).gc)) -#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc)) -#define fvalue(o) check_exp(ttislcf(o), val_(o).f) -#define hvalue(o) check_exp(ttistable(o), gco2t(val_(o).gc)) -#define bvalue(o) check_exp(ttisboolean(o), val_(o).b) -#define thvalue(o) check_exp(ttisthread(o), gco2th(val_(o).gc)) -/* a dead value may get the 'gc' field, but cannot access its contents */ -#define deadvalue(o) check_exp(ttisdeadkey(o), cast(void *, val_(o).gc)) +/* +** By default, entries with any kind of nil are considered empty. +** (In any definition, values associated with absent keys must also +** be accepted as empty.) +*/ +#define isempty(v) ttisnil(v) -#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0)) +/* macro defining a value corresponding to an absent key */ +#define ABSTKEYCONSTANT {NULL}, LUA_VABSTKEY -#define iscollectable(o) (rttype(o) & BIT_ISCOLLECTABLE) +/* mark an entry as empty */ +#define setempty(v) settt_(v, LUA_VEMPTY) -/* Macros for internal tests */ -#define righttt(obj) (ttype(obj) == gcvalue(obj)->tt) -#define checkliveness(g,obj) \ - lua_longassert(!iscollectable(obj) || \ - (righttt(obj) && !isdead(g,gcvalue(obj)))) +/* }================================================================== */ -/* Macros to set values */ -#define settt_(o,t) ((o)->tt_=(t)) -#define setfltvalue(obj,x) \ - { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_TNUMFLT); } +/* +** {================================================================== +** Booleans +** =================================================================== +*/ -#define chgfltvalue(obj,x) \ - { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); } -#define setivalue(obj,x) \ - { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_TNUMINT); } +#define LUA_VFALSE makevariant(LUA_TBOOLEAN, 0) +#define LUA_VTRUE makevariant(LUA_TBOOLEAN, 1) -#define chgivalue(obj,x) \ - { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); } +#define ttisboolean(o) checktype((o), LUA_TBOOLEAN) +#define ttisfalse(o) checktag((o), LUA_VFALSE) +#define ttistrue(o) checktag((o), LUA_VTRUE) -#define setnilvalue(obj) settt_(obj, LUA_TNIL) -#define setfvalue(obj,x) \ - { TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_TLCF); } +#define l_isfalse(o) (ttisfalse(o) || ttisnil(o)) -#define setpvalue(obj,x) \ - { TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_TLIGHTUSERDATA); } -#define setbvalue(obj,x) \ - { TValue *io=(obj); val_(io).b=(x); settt_(io, LUA_TBOOLEAN); } +#define setbfvalue(obj) settt_(obj, LUA_VFALSE) +#define setbtvalue(obj) settt_(obj, LUA_VTRUE) -#define setgcovalue(L,obj,x) \ - { TValue *io = (obj); GCObject *i_g=(x); \ - val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); } +/* }================================================================== */ -#define setsvalue(L,obj,x) \ - { TValue *io = (obj); TString *x_ = (x); \ - val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \ - checkliveness(G(L),io); } -#define setuvalue(L,obj,x) \ - { TValue *io = (obj); Udata *x_ = (x); \ - val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TUSERDATA)); \ - checkliveness(G(L),io); } +/* +** {================================================================== +** Threads +** =================================================================== +*/ + +#define LUA_VTHREAD makevariant(LUA_TTHREAD, 0) + +#define ttisthread(o) checktag((o), ctb(LUA_VTHREAD)) + +#define thvalue(o) check_exp(ttisthread(o), gco2th(val_(o).gc)) #define setthvalue(L,obj,x) \ { TValue *io = (obj); lua_State *x_ = (x); \ - val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTHREAD)); \ - checkliveness(G(L),io); } + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTHREAD)); \ + checkliveness(L,io); } -#define setclLvalue(L,obj,x) \ - { TValue *io = (obj); LClosure *x_ = (x); \ - val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TLCL)); \ - checkliveness(G(L),io); } +#define setthvalue2s(L,o,t) setthvalue(L,s2v(o),t) -#define setclCvalue(L,obj,x) \ - { TValue *io = (obj); CClosure *x_ = (x); \ - val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TCCL)); \ - checkliveness(G(L),io); } +/* }================================================================== */ -#define sethvalue(L,obj,x) \ - { TValue *io = (obj); Table *x_ = (x); \ - val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTABLE)); \ - checkliveness(G(L),io); } -#define setdeadvalue(obj) settt_(obj, LUA_TDEADKEY) +/* +** {================================================================== +** Collectable Objects +** =================================================================== +*/ +/* +** Common Header for all collectable objects (in macro form, to be +** included in other objects) +*/ +#define CommonHeader struct GCObject *next; lu_byte tt; lu_byte marked -#define setobj(L,obj1,obj2) \ - { TValue *io1=(obj1); *io1 = *(obj2); \ - (void)L; checkliveness(G(L),io1); } +/* Common type for all collectable objects */ +typedef struct GCObject { + CommonHeader; +} GCObject; + + +/* Bit mark for collectable types */ +#define BIT_ISCOLLECTABLE (1 << 6) + +#define iscollectable(o) (rawtt(o) & BIT_ISCOLLECTABLE) + +/* mark a tag as collectable */ +#define ctb(t) ((t) | BIT_ISCOLLECTABLE) + +#define gcvalue(o) check_exp(iscollectable(o), val_(o).gc) + +#define gcvalueraw(v) ((v).gc) + +#define setgcovalue(L,obj,x) \ + { TValue *io = (obj); GCObject *i_g=(x); \ + val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); } + +/* }================================================================== */ /* -** different types of assignments, according to destination +** {================================================================== +** Numbers +** =================================================================== */ -/* from stack to (same) stack */ -#define setobjs2s setobj -/* to stack (not from same stack) */ -#define setobj2s setobj -#define setsvalue2s setsvalue -#define sethvalue2s sethvalue -#define setptvalue2s setptvalue -/* from table to same table */ -#define setobjt2t setobj -/* to table */ -#define setobj2t setobj -/* to new object */ -#define setobj2n setobj -#define setsvalue2n setsvalue +/* Variant tags for numbers */ +#define LUA_VNUMINT makevariant(LUA_TNUMBER, 0) /* integer numbers */ +#define LUA_VNUMFLT makevariant(LUA_TNUMBER, 1) /* float numbers */ + +#define ttisnumber(o) checktype((o), LUA_TNUMBER) +#define ttisfloat(o) checktag((o), LUA_VNUMFLT) +#define ttisinteger(o) checktag((o), LUA_VNUMINT) + +#define nvalue(o) check_exp(ttisnumber(o), \ + (ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o))) +#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n) +#define ivalue(o) check_exp(ttisinteger(o), val_(o).i) + +#define fltvalueraw(v) ((v).n) +#define ivalueraw(v) ((v).i) + +#define setfltvalue(obj,x) \ + { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_VNUMFLT); } + +#define chgfltvalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); } + +#define setivalue(obj,x) \ + { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_VNUMINT); } +#define chgivalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); } +/* }================================================================== */ /* -** {====================================================== -** types and prototypes -** ======================================================= +** {================================================================== +** Strings +** =================================================================== */ +/* Variant tags for strings */ +#define LUA_VSHRSTR makevariant(LUA_TSTRING, 0) /* short strings */ +#define LUA_VLNGSTR makevariant(LUA_TSTRING, 1) /* long strings */ -union Value { - GCObject *gc; /* collectable objects */ - void *p; /* light userdata */ - int b; /* booleans */ - lua_CFunction f; /* light C functions */ - lua_Integer i; /* integer numbers */ - lua_Number n; /* float numbers */ -}; - +#define ttisstring(o) checktype((o), LUA_TSTRING) +#define ttisshrstring(o) checktag((o), ctb(LUA_VSHRSTR)) +#define ttislngstring(o) checktag((o), ctb(LUA_VLNGSTR)) -struct lua_TValue { - TValuefields; -}; +#define tsvalueraw(v) (gco2ts((v).gc)) +#define tsvalue(o) check_exp(ttisstring(o), gco2ts(val_(o).gc)) -typedef TValue *StkId; /* index to stack elements */ +#define setsvalue(L,obj,x) \ + { TValue *io = (obj); TString *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \ + checkliveness(L,io); } +/* set a string to the stack */ +#define setsvalue2s(L,o,s) setsvalue(L,s2v(o),s) +/* set a string to a new object */ +#define setsvalue2n setsvalue /* -** Header for string value; string bytes follow the end of this structure -** (aligned according to 'UTString'; see next). +** Header for a string value. */ typedef struct TString { CommonHeader; lu_byte extra; /* reserved words for short strings; "has hash" for longs */ - lu_byte shrlen; /* length for short strings */ + lu_byte shrlen; /* length for short strings, 0xFF for long strings */ unsigned int hash; union { size_t lnglen; /* length for long strings */ struct TString *hnext; /* linked list for hash table */ } u; + char contents[1]; } TString; + /* -** Ensures that address after this type is always fully aligned. +** Get the actual string (array of bytes) from a 'TString'. (Generic +** version and specialized versions for long and short strings.) */ -typedef union UTString { - L_Umaxalign dummy; /* ensures maximum alignment for strings */ - TString tsv; -} UTString; +#define getstr(ts) ((ts)->contents) +#define getlngstr(ts) check_exp((ts)->shrlen == 0xFF, (ts)->contents) +#define getshrstr(ts) check_exp((ts)->shrlen != 0xFF, (ts)->contents) + + +/* get string length from 'TString *s' */ +#define tsslen(s) \ + ((s)->shrlen != 0xFF ? (s)->shrlen : (s)->u.lnglen) + +/* }================================================================== */ /* -** Get the actual string (array of bytes) from a 'TString'. -** (Access to 'extra' ensures that value is really a 'TString'.) +** {================================================================== +** Userdata +** =================================================================== */ -#define getaddrstr(ts) (cast(char *, (ts)) + sizeof(UTString)) -#define getstr(ts) \ - check_exp(sizeof((ts)->extra), cast(const char*, getaddrstr(ts))) -/* get the actual string (array of bytes) from a Lua value */ -#define svalue(o) getstr(tsvalue(o)) -/* get string length from 'TString *s' */ -#define tsslen(s) ((s)->tt == LUA_TSHRSTR ? (s)->shrlen : (s)->u.lnglen) +/* +** Light userdata should be a variant of userdata, but for compatibility +** reasons they are also different types. +*/ +#define LUA_VLIGHTUSERDATA makevariant(LUA_TLIGHTUSERDATA, 0) + +#define LUA_VUSERDATA makevariant(LUA_TUSERDATA, 0) -/* get string length from 'TValue *o' */ -#define vslen(o) tsslen(tsvalue(o)) +#define ttislightuserdata(o) checktag((o), LUA_VLIGHTUSERDATA) +#define ttisfulluserdata(o) checktag((o), ctb(LUA_VUSERDATA)) + +#define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p) +#define uvalue(o) check_exp(ttisfulluserdata(o), gco2u(val_(o).gc)) + +#define pvalueraw(v) ((v).p) + +#define setpvalue(obj,x) \ + { TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_VLIGHTUSERDATA); } + +#define setuvalue(L,obj,x) \ + { TValue *io = (obj); Udata *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VUSERDATA)); \ + checkliveness(L,io); } + + +/* Ensures that addresses after this type are always fully aligned. */ +typedef union UValue { + TValue uv; + LUAI_MAXALIGN; /* ensures maximum alignment for udata bytes */ +} UValue; /* -** Header for userdata; memory area follows the end of this structure -** (aligned according to 'UUdata'; see next). +** Header for userdata with user values; +** memory area follows the end of this structure. */ typedef struct Udata { CommonHeader; - lu_byte ttuv_; /* user value's tag */ - struct Table *metatable; + unsigned short nuvalue; /* number of user values */ size_t len; /* number of bytes */ - union Value user_; /* user value */ + struct Table *metatable; + GCObject *gclist; + UValue uv[1]; /* user values */ } Udata; /* -** Ensures that address after this type is always fully aligned. +** Header for userdata with no user values. These userdata do not need +** to be gray during GC, and therefore do not need a 'gclist' field. +** To simplify, the code always use 'Udata' for both kinds of userdata, +** making sure it never accesses 'gclist' on userdata with no user values. +** This structure here is used only to compute the correct size for +** this representation. (The 'bindata' field in its end ensures correct +** alignment for binary data following this header.) */ -typedef union UUdata { - L_Umaxalign dummy; /* ensures maximum alignment for 'local' udata */ - Udata uv; -} UUdata; +typedef struct Udata0 { + CommonHeader; + unsigned short nuvalue; /* number of user values */ + size_t len; /* number of bytes */ + struct Table *metatable; + union {LUAI_MAXALIGN;} bindata; +} Udata0; -/* -** Get the address of memory block inside 'Udata'. -** (Access to 'ttuv_' ensures that value is really a 'Udata'.) -*/ -#define getudatamem(u) \ - check_exp(sizeof((u)->ttuv_), (cast(char*, (u)) + sizeof(UUdata))) +/* compute the offset of the memory area of a userdata */ +#define udatamemoffset(nuv) \ + ((nuv) == 0 ? offsetof(Udata0, bindata) \ + : offsetof(Udata, uv) + (sizeof(UValue) * (nuv))) + +/* get the address of the memory block inside 'Udata' */ +#define getudatamem(u) (cast_charp(u) + udatamemoffset((u)->nuvalue)) + +/* compute the size of a userdata */ +#define sizeudata(nuv,nb) (udatamemoffset(nuv) + (nb)) + +/* }================================================================== */ -#define setuservalue(L,u,o) \ - { const TValue *io=(o); Udata *iu = (u); \ - iu->user_ = io->value_; iu->ttuv_ = rttype(io); \ - checkliveness(G(L),io); } +/* +** {================================================================== +** Prototypes +** =================================================================== +*/ -#define getuservalue(L,u,o) \ - { TValue *io=(o); const Udata *iu = (u); \ - io->value_ = iu->user_; settt_(io, iu->ttuv_); \ - checkliveness(G(L),io); } +#define LUA_VPROTO makevariant(LUA_TPROTO, 0) /* @@ -391,6 +514,7 @@ typedef struct Upvaldesc { TString *name; /* upvalue name (for debug information) */ lu_byte instack; /* whether it is in stack (register) */ lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ + lu_byte kind; /* kind of corresponding variable */ } Upvaldesc; @@ -405,12 +529,27 @@ typedef struct LocVar { } LocVar; +/* +** Associates the absolute line source for a given instruction ('pc'). +** The array 'lineinfo' gives, for each instruction, the difference in +** lines from the previous instruction. When that difference does not +** fit into a byte, Lua saves the absolute line for that instruction. +** (Lua also saves the absolute line periodically, to speed up the +** computation of a line number: we can use binary search in the +** absolute-line array, but we must traverse the 'lineinfo' array +** linearly to compute a line.) +*/ +typedef struct AbsLineInfo { + int pc; + int line; +} AbsLineInfo; + /* ** Function Prototypes */ typedef struct Proto { CommonHeader; - lu_byte numparams; /* number of fixed parameters */ + lu_byte numparams; /* number of fixed (named) parameters */ lu_byte is_vararg; lu_byte maxstacksize; /* number of registers needed by this function */ int sizeupvalues; /* size of 'upvalues' */ @@ -419,30 +558,88 @@ typedef struct Proto { int sizelineinfo; int sizep; /* size of 'p' */ int sizelocvars; - int linedefined; - int lastlinedefined; + int sizeabslineinfo; /* size of 'abslineinfo' */ + int linedefined; /* debug information */ + int lastlinedefined; /* debug information */ TValue *k; /* constants used by the function */ Instruction *code; /* opcodes */ struct Proto **p; /* functions defined inside the function */ - int *lineinfo; /* map from opcodes to source lines (debug information) */ - LocVar *locvars; /* information about local variables (debug information) */ Upvaldesc *upvalues; /* upvalue information */ - struct LClosure *cache; /* last-created closure with this prototype */ + ls_byte *lineinfo; /* information about source lines (debug information) */ + AbsLineInfo *abslineinfo; /* idem */ + LocVar *locvars; /* information about local variables (debug information) */ TString *source; /* used for debug information */ GCObject *gclist; } Proto; +/* }================================================================== */ /* -** Lua Upvalues +** {================================================================== +** Functions +** =================================================================== */ -typedef struct UpVal UpVal; + +#define LUA_VUPVAL makevariant(LUA_TUPVAL, 0) + + +/* Variant tags for functions */ +#define LUA_VLCL makevariant(LUA_TFUNCTION, 0) /* Lua closure */ +#define LUA_VLCF makevariant(LUA_TFUNCTION, 1) /* light C function */ +#define LUA_VCCL makevariant(LUA_TFUNCTION, 2) /* C closure */ + +#define ttisfunction(o) checktype(o, LUA_TFUNCTION) +#define ttisLclosure(o) checktag((o), ctb(LUA_VLCL)) +#define ttislcf(o) checktag((o), LUA_VLCF) +#define ttisCclosure(o) checktag((o), ctb(LUA_VCCL)) +#define ttisclosure(o) (ttisLclosure(o) || ttisCclosure(o)) + + +#define isLfunction(o) ttisLclosure(o) + +#define clvalue(o) check_exp(ttisclosure(o), gco2cl(val_(o).gc)) +#define clLvalue(o) check_exp(ttisLclosure(o), gco2lcl(val_(o).gc)) +#define fvalue(o) check_exp(ttislcf(o), val_(o).f) +#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc)) + +#define fvalueraw(v) ((v).f) + +#define setclLvalue(L,obj,x) \ + { TValue *io = (obj); LClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VLCL)); \ + checkliveness(L,io); } + +#define setclLvalue2s(L,o,cl) setclLvalue(L,s2v(o),cl) + +#define setfvalue(obj,x) \ + { TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_VLCF); } + +#define setclCvalue(L,obj,x) \ + { TValue *io = (obj); CClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VCCL)); \ + checkliveness(L,io); } /* -** Closures +** Upvalues for Lua closures */ +typedef struct UpVal { + CommonHeader; + union { + TValue *p; /* points to stack or to its own value */ + ptrdiff_t offset; /* used while the stack is being reallocated */ + } v; + union { + struct { /* (when open) */ + struct UpVal *next; /* linked list */ + struct UpVal **previous; + } open; + TValue value; /* the value (when closed) */ + } u; +} UpVal; + + #define ClosureHeader \ CommonHeader; lu_byte nupvalues; GCObject *gclist @@ -467,42 +664,81 @@ typedef union Closure { } Closure; -#define isLfunction(o) ttisLclosure(o) - #define getproto(o) (clLvalue(o)->p) +/* }================================================================== */ + /* +** {================================================================== ** Tables +** =================================================================== */ -typedef union TKey { - struct { - TValuefields; - int next; /* for chaining (offset for next node) */ - } nk; - TValue tvk; -} TKey; +#define LUA_VTABLE makevariant(LUA_TTABLE, 0) +#define ttistable(o) checktag((o), ctb(LUA_VTABLE)) -/* copy a value into a key without messing up field 'next' */ -#define setnodekey(L,key,obj) \ - { TKey *k_=(key); const TValue *io_=(obj); \ - k_->nk.value_ = io_->value_; k_->nk.tt_ = io_->tt_; \ - (void)L; checkliveness(G(L),io_); } +#define hvalue(o) check_exp(ttistable(o), gco2t(val_(o).gc)) +#define sethvalue(L,obj,x) \ + { TValue *io = (obj); Table *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTABLE)); \ + checkliveness(L,io); } -typedef struct Node { - TValue i_val; - TKey i_key; +#define sethvalue2s(L,o,h) sethvalue(L,s2v(o),h) + + +/* +** Nodes for Hash tables: A pack of two TValue's (key-value pairs) +** plus a 'next' field to link colliding entries. The distribution +** of the key's fields ('key_tt' and 'key_val') not forming a proper +** 'TValue' allows for a smaller size for 'Node' both in 4-byte +** and 8-byte alignments. +*/ +typedef union Node { + struct NodeKey { + TValuefields; /* fields for value */ + lu_byte key_tt; /* key type */ + int next; /* for chaining */ + Value key_val; /* key value */ + } u; + TValue i_val; /* direct access to node's value as a proper 'TValue' */ } Node; +/* copy a value into a key */ +#define setnodekey(L,node,obj) \ + { Node *n_=(node); const TValue *io_=(obj); \ + n_->u.key_val = io_->value_; n_->u.key_tt = io_->tt_; \ + checkliveness(L,io_); } + + +/* copy a value from a key */ +#define getnodekey(L,obj,node) \ + { TValue *io_=(obj); const Node *n_=(node); \ + io_->value_ = n_->u.key_val; io_->tt_ = n_->u.key_tt; \ + checkliveness(L,io_); } + + +/* +** About 'alimit': if 'isrealasize(t)' is true, then 'alimit' is the +** real size of 'array'. Otherwise, the real size of 'array' is the +** smallest power of two not smaller than 'alimit' (or zero iff 'alimit' +** is zero); 'alimit' is then used as a hint for #t. +*/ + +#define BITRAS (1 << 7) +#define isrealasize(t) (!((t)->flags & BITRAS)) +#define setrealasize(t) ((t)->flags &= cast_byte(~BITRAS)) +#define setnorealasize(t) ((t)->flags |= BITRAS) + + typedef struct Table { CommonHeader; lu_byte flags; /* 1<

u.key_tt) +#define keyval(node) ((node)->u.key_val) + +#define keyisnil(node) (keytt(node) == LUA_TNIL) +#define keyisinteger(node) (keytt(node) == LUA_VNUMINT) +#define keyival(node) (keyval(node).i) +#define keyisshrstr(node) (keytt(node) == ctb(LUA_VSHRSTR)) +#define keystrval(node) (gco2ts(keyval(node).gc)) + +#define setnilkey(node) (keytt(node) = LUA_TNIL) + +#define keyiscollectable(n) (keytt(n) & BIT_ISCOLLECTABLE) + +#define gckey(n) (keyval(n).gc) +#define gckeyN(n) (keyiscollectable(n) ? gckey(n) : NULL) + /* -** 'module' operation for hashing (size is always a power of 2) +** Dead keys in tables have the tag DEADKEY but keep their original +** gcvalue. This distinguishes them from regular keys but allows them to +** be found when searched in a special way. ('next' needs that to find +** keys removed from a table during a traversal.) */ -#define lmod(s,size) \ - (check_exp((size&(size-1))==0, (cast(int, (s) & ((size)-1))))) +#define setdeadkey(node) (keytt(node) = LUA_TDEADKEY) +#define keyisdead(node) (keytt(node) == LUA_TDEADKEY) +/* }================================================================== */ -#define twoto(x) (1<<(x)) -#define sizenode(t) (twoto((t)->lsizenode)) /* -** (address of) a fixed nil value +** 'module' operation for hashing (size is always a power of 2) */ -#define luaO_nilobject (&luaO_nilobject_) +#define lmod(s,size) \ + (check_exp((size&(size-1))==0, (cast_int((s) & ((size)-1))))) + +#define twoto(x) (1<<(x)) +#define sizenode(t) (twoto((t)->lsizenode)) -LUAI_DDEC const TValue luaO_nilobject_; /* size of buffer for 'luaO_utf8esc' function */ #define UTF8BUFFSZ 8 -LUAI_FUNC int luaO_int2fb (unsigned int x); -LUAI_FUNC int luaO_fb2int (int x); LUAI_FUNC int luaO_utf8esc (char *buff, unsigned long x); LUAI_FUNC int luaO_ceillog2 (unsigned int x); +LUAI_FUNC int luaO_rawarith (lua_State *L, int op, const TValue *p1, + const TValue *p2, TValue *res); LUAI_FUNC void luaO_arith (lua_State *L, int op, const TValue *p1, - const TValue *p2, TValue *res); + const TValue *p2, StkId res); LUAI_FUNC size_t luaO_str2num (const char *s, TValue *o); LUAI_FUNC int luaO_hexavalue (int c); -LUAI_FUNC void luaO_tostring (lua_State *L, StkId obj); +LUAI_FUNC void luaO_tostring (lua_State *L, TValue *obj); LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp); LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); -LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t len); +LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t srclen); #endif diff --git a/libs/lua/lopcodes.c b/libs/lua/lopcodes.c index a1cbef85..c67aa227 100644 --- a/libs/lua/lopcodes.c +++ b/libs/lua/lopcodes.c @@ -1,5 +1,5 @@ /* -** $Id: lopcodes.c,v 1.55 2015/01/05 13:48:33 roberto Exp $ +** $Id: lopcodes.c $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ @@ -10,115 +10,95 @@ #include "lprefix.h" -#include - #include "lopcodes.h" /* ORDER OP */ -LUAI_DDEF const char *const luaP_opnames[NUM_OPCODES+1] = { - "MOVE", - "LOADK", - "LOADKX", - "LOADBOOL", - "LOADNIL", - "GETUPVAL", - "GETTABUP", - "GETTABLE", - "SETTABUP", - "SETUPVAL", - "SETTABLE", - "NEWTABLE", - "SELF", - "ADD", - "SUB", - "MUL", - "MOD", - "POW", - "DIV", - "IDIV", - "BAND", - "BOR", - "BXOR", - "SHL", - "SHR", - "UNM", - "BNOT", - "NOT", - "LEN", - "CONCAT", - "JMP", - "EQ", - "LT", - "LE", - "TEST", - "TESTSET", - "CALL", - "TAILCALL", - "RETURN", - "FORLOOP", - "FORPREP", - "TFORCALL", - "TFORLOOP", - "SETLIST", - "CLOSURE", - "VARARG", - "EXTRAARG", - NULL -}; - - -#define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m)) - LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { -/* T A B C mode opcode */ - opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */ - ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */ - ,opmode(0, 1, OpArgN, OpArgN, iABx) /* OP_LOADKX */ - ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */ - ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_LOADNIL */ - ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */ - ,opmode(0, 1, OpArgU, OpArgK, iABC) /* OP_GETTABUP */ - ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */ - ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABUP */ - ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */ - ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */ - ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */ - ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_IDIV */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BAND */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BOR */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_BXOR */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SHL */ - ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SHR */ - ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ - ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_BNOT */ - ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ - ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ - ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ - ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */ - ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */ - ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */ - ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */ - ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TEST */ - ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */ - ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */ - ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */ - ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */ - ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */ - ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */ - ,opmode(0, 0, OpArgN, OpArgU, iABC) /* OP_TFORCALL */ - ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_TFORLOOP */ - ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */ - ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */ - ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */ - ,opmode(0, 0, OpArgU, OpArgU, iAx) /* OP_EXTRAARG */ +/* MM OT IT T A mode opcode */ + opmode(0, 0, 0, 0, 1, iABC) /* OP_MOVE */ + ,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADI */ + ,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADF */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADK */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADKX */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADFALSE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LFALSESKIP */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADTRUE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADNIL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETUPVAL */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETUPVAL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABUP */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABLE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETFIELD */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABUP */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABLE */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETI */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETFIELD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_NEWTABLE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SELF */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUBK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MULK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MODK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_POWK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIVK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIVK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BANDK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BORK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXORK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHRI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHLI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUB */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MUL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MOD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_POW */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIV */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIV */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BAND */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BOR */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXOR */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHR */ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBIN */ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINI*/ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINK*/ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_UNM */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BNOT */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_NOT */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LEN */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_CONCAT */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_CLOSE */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_TBC */ + ,opmode(0, 0, 0, 0, 0, isJ) /* OP_JMP */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQ */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LT */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LE */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQK */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LTI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LEI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_GTI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_GEI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_TEST */ + ,opmode(0, 0, 0, 1, 1, iABC) /* OP_TESTSET */ + ,opmode(0, 1, 1, 0, 1, iABC) /* OP_CALL */ + ,opmode(0, 1, 1, 0, 1, iABC) /* OP_TAILCALL */ + ,opmode(0, 0, 1, 0, 0, iABC) /* OP_RETURN */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN0 */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN1 */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORLOOP */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORPREP */ + ,opmode(0, 0, 0, 0, 0, iABx) /* OP_TFORPREP */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_TFORCALL */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_TFORLOOP */ + ,opmode(0, 0, 1, 0, 0, iABC) /* OP_SETLIST */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ + ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ + ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ + ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ }; diff --git a/libs/lua/lopcodes.h b/libs/lua/lopcodes.h index 864b8e4b..46911cac 100644 --- a/libs/lua/lopcodes.h +++ b/libs/lua/lopcodes.h @@ -1,5 +1,5 @@ /* -** $Id: lopcodes.h,v 1.148 2014/10/25 11:50:46 roberto Exp $ +** $Id: lopcodes.h $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ @@ -11,69 +11,94 @@ /*=========================================================================== - We assume that instructions are unsigned numbers. - All instructions have an opcode in the first 6 bits. - Instructions can have the following fields: - 'A' : 8 bits - 'B' : 9 bits - 'C' : 9 bits - 'Ax' : 26 bits ('A', 'B', and 'C' together) - 'Bx' : 18 bits ('B' and 'C' together) - 'sBx' : signed Bx - - A signed argument is represented in excess K; that is, the number - value is the unsigned value minus K. K is exactly the maximum value - for that argument (so that -max is represented by 0, and +max is - represented by 2*max), which is half the maximum for the corresponding - unsigned argument. + We assume that instructions are unsigned 32-bit integers. + All instructions have an opcode in the first 7 bits. + Instructions can have the following formats: + + 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 + 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +iABC C(8) | B(8) |k| A(8) | Op(7) | +iABx Bx(17) | A(8) | Op(7) | +iAsBx sBx (signed)(17) | A(8) | Op(7) | +iAx Ax(25) | Op(7) | +isJ sJ (signed)(25) | Op(7) | + + A signed argument is represented in excess K: the represented value is + the written unsigned value minus K, where K is half the maximum for the + corresponding unsigned argument. ===========================================================================*/ -enum OpMode {iABC, iABx, iAsBx, iAx}; /* basic instruction format */ +enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */ /* ** size and position of opcode arguments. */ -#define SIZE_C 9 -#define SIZE_B 9 -#define SIZE_Bx (SIZE_C + SIZE_B) +#define SIZE_C 8 +#define SIZE_B 8 +#define SIZE_Bx (SIZE_C + SIZE_B + 1) #define SIZE_A 8 -#define SIZE_Ax (SIZE_C + SIZE_B + SIZE_A) +#define SIZE_Ax (SIZE_Bx + SIZE_A) +#define SIZE_sJ (SIZE_Bx + SIZE_A) -#define SIZE_OP 6 +#define SIZE_OP 7 #define POS_OP 0 + #define POS_A (POS_OP + SIZE_OP) -#define POS_C (POS_A + SIZE_A) -#define POS_B (POS_C + SIZE_C) -#define POS_Bx POS_C +#define POS_k (POS_A + SIZE_A) +#define POS_B (POS_k + 1) +#define POS_C (POS_B + SIZE_B) + +#define POS_Bx POS_k + #define POS_Ax POS_A +#define POS_sJ POS_A + /* ** limits for opcode arguments. -** we use (signed) int to manipulate most arguments, -** so they must fit in LUAI_BITSINT-1 bits (-1 for sign) +** we use (signed) 'int' to manipulate most arguments, +** so they must fit in ints. */ -#if SIZE_Bx < LUAI_BITSINT-1 -#define MAXARG_Bx ((1<>1) /* 'sBx' is signed */ + +/* Check whether type 'int' has at least 'b' bits ('b' < 32) */ +#define L_INTHASBITS(b) ((UINT_MAX >> ((b) - 1)) >= 1) + + +#if L_INTHASBITS(SIZE_Bx) +#define MAXARG_Bx ((1<>1) /* 'sBx' is signed */ + + +#if L_INTHASBITS(SIZE_Ax) #define MAXARG_Ax ((1<> 1) -#define MAXARG_A ((1<> 1) + +#define int2sC(i) ((i) + OFFSET_sC) +#define sC2int(i) ((i) - OFFSET_sC) /* creates a mask with 'n' 1 bits at position 'p' */ @@ -90,33 +115,49 @@ enum OpMode {iABC, iABx, iAsBx, iAx}; /* basic instruction format */ #define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ ((cast(Instruction, o)<>pos) & MASK1(size,0))) +#define checkopm(i,m) (getOpMode(GET_OPCODE(i)) == m) + + +#define getarg(i,pos,size) (cast_int(((i)>>(pos)) & MASK1(size,0))) #define setarg(i,v,pos,size) ((i) = (((i)&MASK0(size,pos)) | \ ((cast(Instruction, v)<> RK(C) */ -OP_UNM,/* A B R(A) := -R(B) */ -OP_BNOT,/* A B R(A) := ~R(B) */ -OP_NOT,/* A B R(A) := not R(B) */ -OP_LEN,/* A B R(A) := length of R(B) */ - -OP_CONCAT,/* A B C R(A) := R(B).. ... ..R(C) */ - -OP_JMP,/* A sBx pc+=sBx; if (A) close all upvalues >= R(A - 1) */ -OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ -OP_LT,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ -OP_LE,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ - -OP_TEST,/* A C if not (R(A) <=> C) then pc++ */ -OP_TESTSET,/* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ - -OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ -OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ -OP_RETURN,/* A B return R(A), ... ,R(A+B-2) (see note) */ - -OP_FORLOOP,/* A sBx R(A)+=R(A+2); - if R(A) > sC */ +OP_SHLI,/* A B sC R[A] := sC << R[B] */ + +OP_ADD,/* A B C R[A] := R[B] + R[C] */ +OP_SUB,/* A B C R[A] := R[B] - R[C] */ +OP_MUL,/* A B C R[A] := R[B] * R[C] */ +OP_MOD,/* A B C R[A] := R[B] % R[C] */ +OP_POW,/* A B C R[A] := R[B] ^ R[C] */ +OP_DIV,/* A B C R[A] := R[B] / R[C] */ +OP_IDIV,/* A B C R[A] := R[B] // R[C] */ + +OP_BAND,/* A B C R[A] := R[B] & R[C] */ +OP_BOR,/* A B C R[A] := R[B] | R[C] */ +OP_BXOR,/* A B C R[A] := R[B] ~ R[C] */ +OP_SHL,/* A B C R[A] := R[B] << R[C] */ +OP_SHR,/* A B C R[A] := R[B] >> R[C] */ + +OP_MMBIN,/* A B C call C metamethod over R[A] and R[B] (*) */ +OP_MMBINI,/* A sB C k call C metamethod over R[A] and sB */ +OP_MMBINK,/* A B C k call C metamethod over R[A] and K[B] */ + +OP_UNM,/* A B R[A] := -R[B] */ +OP_BNOT,/* A B R[A] := ~R[B] */ +OP_NOT,/* A B R[A] := not R[B] */ +OP_LEN,/* A B R[A] := #R[B] (length operator) */ + +OP_CONCAT,/* A B R[A] := R[A].. ... ..R[A + B - 1] */ + +OP_CLOSE,/* A close all upvalues >= R[A] */ +OP_TBC,/* A mark variable A "to be closed" */ +OP_JMP,/* sJ pc += sJ */ +OP_EQ,/* A B k if ((R[A] == R[B]) ~= k) then pc++ */ +OP_LT,/* A B k if ((R[A] < R[B]) ~= k) then pc++ */ +OP_LE,/* A B k if ((R[A] <= R[B]) ~= k) then pc++ */ + +OP_EQK,/* A B k if ((R[A] == K[B]) ~= k) then pc++ */ +OP_EQI,/* A sB k if ((R[A] == sB) ~= k) then pc++ */ +OP_LTI,/* A sB k if ((R[A] < sB) ~= k) then pc++ */ +OP_LEI,/* A sB k if ((R[A] <= sB) ~= k) then pc++ */ +OP_GTI,/* A sB k if ((R[A] > sB) ~= k) then pc++ */ +OP_GEI,/* A sB k if ((R[A] >= sB) ~= k) then pc++ */ + +OP_TEST,/* A k if (not R[A] == k) then pc++ */ +OP_TESTSET,/* A B k if (not R[B] == k) then pc++ else R[A] := R[B] (*) */ + +OP_CALL,/* A B C R[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1]) */ +OP_TAILCALL,/* A B C k return R[A](R[A+1], ... ,R[A+B-1]) */ + +OP_RETURN,/* A B C k return R[A], ... ,R[A+B-2] (see note) */ +OP_RETURN0,/* return */ +OP_RETURN1,/* A return R[A] */ + +OP_FORLOOP,/* A Bx update counters; if loop continues then pc-=Bx; */ +OP_FORPREP,/* A Bx ; + if not to run then pc+=Bx+1; */ + +OP_TFORPREP,/* A Bx create upvalue for R[A + 3]; pc+=Bx */ +OP_TFORCALL,/* A C R[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2]); */ +OP_TFORLOOP,/* A Bx if R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx } */ + +OP_SETLIST,/* A B C k R[A][C+i] := R[A+i], 1 <= i <= B */ + +OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ + +OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ -OP_SETLIST,/* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */ - -OP_CLOSURE,/* A Bx R(A) := closure(KPROTO[Bx]) */ - -OP_VARARG,/* A B R(A), R(A+1), ..., R(A+B-2) = vararg */ +OP_VARARGPREP,/*A (adjust vararg parameters) */ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ } OpCode; -#define NUM_OPCODES (cast(int, OP_EXTRAARG) + 1) +#define NUM_OPCODES ((int)(OP_EXTRAARG) + 1) /*=========================================================================== Notes: - (*) In OP_CALL, if (B == 0) then B = top. If (C == 0), then 'top' is - set to last_result+1, so next open instruction (OP_CALL, OP_RETURN, - OP_SETLIST) may use 'top'. - (*) In OP_VARARG, if (B == 0) then use actual number of varargs and + (*) Opcode OP_LFALSESKIP is used to convert a condition to a boolean + value, in a code equivalent to (not cond ? false : true). (It + produces false and skips the next instruction producing true.) + + (*) Opcodes OP_MMBIN and variants follow each arithmetic and + bitwise opcode. If the operation succeeds, it skips this next + opcode. Otherwise, this opcode calls the corresponding metamethod. + + (*) Opcode OP_TESTSET is used in short-circuit expressions that need + both to jump and to produce a value, such as (a = b or c). + + (*) In OP_CALL, if (B == 0) then B = top - A. If (C == 0), then + 'top' is set to last_result+1, so next open instruction (OP_CALL, + OP_RETURN*, OP_SETLIST) may use 'top'. + + (*) In OP_VARARG, if (C == 0) then use actual number of varargs and set top (like in OP_CALL with C == 0). (*) In OP_RETURN, if (B == 0) then return up to 'top'. - (*) In OP_SETLIST, if (B == 0) then B = 'top'; if (C == 0) then next - 'instruction' is EXTRAARG(real C). + (*) In OP_LOADKX and OP_NEWTABLE, the next instruction is always + OP_EXTRAARG. - (*) In OP_LOADKX, the next 'instruction' is always EXTRAARG. + (*) In OP_SETLIST, if (B == 0) then real B = 'top'; if k, then + real C = EXTRAARG _ C (the bits of EXTRAARG concatenated with the + bits of C). - (*) For comparisons, A specifies what condition the test should accept + (*) In OP_NEWTABLE, B is log2 of the hash size (which is always a + power of 2) plus 1, or zero for size zero. If not k, the array size + is C. Otherwise, the array size is EXTRAARG _ C. + + (*) For comparisons, k specifies what condition the test should accept (true or false). + (*) In OP_MMBINI/OP_MMBINK, k means the arguments were flipped + (the constant is the first operand). + (*) All 'skips' (pc++) assume that next instruction is a jump. + (*) In instructions OP_RETURN/OP_TAILCALL, 'k' specifies that the + function builds upvalues, which may need to be closed. C > 0 means + the function is vararg, so that its 'func' must be corrected before + returning; in this case, (C - 1) is its number of fixed parameters. + + (*) In comparisons with an immediate operand, C signals whether the + original operand was a float. (It must be corrected in case of + metamethods.) + ===========================================================================*/ /* ** masks for instruction properties. The format is: -** bits 0-1: op mode -** bits 2-3: C arg mode -** bits 4-5: B arg mode -** bit 6: instruction set register A -** bit 7: operator is a test (next instruction must be a jump) +** bits 0-2: op mode +** bit 3: instruction set register A +** bit 4: operator is a test (next instruction must be a jump) +** bit 5: instruction uses 'L->top' set by previous instruction (when B == 0) +** bit 6: instruction sets 'L->top' for next instruction (when C == 0) +** bit 7: instruction is an MM instruction (call a metamethod) */ -enum OpArgMask { - OpArgN, /* argument is not used */ - OpArgU, /* argument is used */ - OpArgR, /* argument is a register or a jump offset */ - OpArgK /* argument is a constant or register/constant */ -}; +LUAI_DDEC(const lu_byte luaP_opmodes[NUM_OPCODES];) -LUAI_DDEC const lu_byte luaP_opmodes[NUM_OPCODES]; +#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 7)) +#define testAMode(m) (luaP_opmodes[m] & (1 << 3)) +#define testTMode(m) (luaP_opmodes[m] & (1 << 4)) +#define testITMode(m) (luaP_opmodes[m] & (1 << 5)) +#define testOTMode(m) (luaP_opmodes[m] & (1 << 6)) +#define testMMMode(m) (luaP_opmodes[m] & (1 << 7)) -#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 3)) -#define getBMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 4) & 3)) -#define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3)) -#define testAMode(m) (luaP_opmodes[m] & (1 << 6)) -#define testTMode(m) (luaP_opmodes[m] & (1 << 7)) +/* "out top" (set top for next instruction) */ +#define isOT(i) \ + ((testOTMode(GET_OPCODE(i)) && GETARG_C(i) == 0) || \ + GET_OPCODE(i) == OP_TAILCALL) +/* "in top" (uses top from previous instruction) */ +#define isIT(i) (testITMode(GET_OPCODE(i)) && GETARG_B(i) == 0) -LUAI_DDEC const char *const luaP_opnames[NUM_OPCODES+1]; /* opcode names */ +#define opmode(mm,ot,it,t,a,m) \ + (((mm) << 7) | ((ot) << 6) | ((it) << 5) | ((t) << 4) | ((a) << 3) | (m)) /* number of list items to accumulate before a SETLIST instruction */ #define LFIELDS_PER_FLUSH 50 - #endif diff --git a/libs/lua/lopnames.h b/libs/lua/lopnames.h new file mode 100644 index 00000000..965cec9b --- /dev/null +++ b/libs/lua/lopnames.h @@ -0,0 +1,103 @@ +/* +** $Id: lopnames.h $ +** Opcode names +** See Copyright Notice in lua.h +*/ + +#if !defined(lopnames_h) +#define lopnames_h + +#include + + +/* ORDER OP */ + +static const char *const opnames[] = { + "MOVE", + "LOADI", + "LOADF", + "LOADK", + "LOADKX", + "LOADFALSE", + "LFALSESKIP", + "LOADTRUE", + "LOADNIL", + "GETUPVAL", + "SETUPVAL", + "GETTABUP", + "GETTABLE", + "GETI", + "GETFIELD", + "SETTABUP", + "SETTABLE", + "SETI", + "SETFIELD", + "NEWTABLE", + "SELF", + "ADDI", + "ADDK", + "SUBK", + "MULK", + "MODK", + "POWK", + "DIVK", + "IDIVK", + "BANDK", + "BORK", + "BXORK", + "SHRI", + "SHLI", + "ADD", + "SUB", + "MUL", + "MOD", + "POW", + "DIV", + "IDIV", + "BAND", + "BOR", + "BXOR", + "SHL", + "SHR", + "MMBIN", + "MMBINI", + "MMBINK", + "UNM", + "BNOT", + "NOT", + "LEN", + "CONCAT", + "CLOSE", + "TBC", + "JMP", + "EQ", + "LT", + "LE", + "EQK", + "EQI", + "LTI", + "LEI", + "GTI", + "GEI", + "TEST", + "TESTSET", + "CALL", + "TAILCALL", + "RETURN", + "RETURN0", + "RETURN1", + "FORLOOP", + "FORPREP", + "TFORPREP", + "TFORCALL", + "TFORLOOP", + "SETLIST", + "CLOSURE", + "VARARG", + "VARARGPREP", + "EXTRAARG", + NULL +}; + +#endif + diff --git a/libs/lua/lparser.c b/libs/lua/lparser.c index 9a54dfc9..2b888c7c 100644 --- a/libs/lua/lparser.c +++ b/libs/lua/lparser.c @@ -1,5 +1,5 @@ /* -** $Id: lparser.c,v 2.147 2014/12/27 20:31:43 roberto Exp $ +** $Id: lparser.c $ ** Lua Parser ** See Copyright Notice in lua.h */ @@ -10,6 +10,7 @@ #include "lprefix.h" +#include #include #include "lua.h" @@ -52,6 +53,7 @@ typedef struct BlockCnt { lu_byte nactvar; /* # active locals outside the block */ lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isloop; /* true if 'block' is a loop */ + lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ } BlockCnt; @@ -63,13 +65,6 @@ static void statement (LexState *ls); static void expr (LexState *ls, expdesc *v); -/* semantic error */ -static l_noret semerror (LexState *ls, const char *msg) { - ls->t.token = 0; /* remove "near " from final message */ - luaX_syntaxerror(ls, msg); -} - - static l_noret error_expected (LexState *ls, int token) { luaX_syntaxerror(ls, luaO_pushfstring(ls->L, "%s expected", luaX_token2str(ls, token))); @@ -94,6 +89,9 @@ static void checklimit (FuncState *fs, int v, int l, const char *what) { } +/* +** Test whether next token is 'c'; if so, skip it. +*/ static int testnext (LexState *ls, int c) { if (ls->t.token == c) { luaX_next(ls); @@ -103,12 +101,18 @@ static int testnext (LexState *ls, int c) { } +/* +** Check that next token is 'c'. +*/ static void check (LexState *ls, int c) { if (ls->t.token != c) error_expected(ls, c); } +/* +** Check that next token is 'c' and skip it. +*/ static void checknext (LexState *ls, int c) { check(ls, c); luaX_next(ls); @@ -118,11 +122,15 @@ static void checknext (LexState *ls, int c) { #define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); } - +/* +** Check that next token is 'what' and skip it. In case of error, +** raise an error that the expected 'what' should match a 'who' +** in line 'where' (if that is not the current line). +*/ static void check_match (LexState *ls, int what, int who, int where) { - if (!testnext(ls, what)) { - if (where == ls->linenumber) - error_expected(ls, what); + if (l_unlikely(!testnext(ls, what))) { + if (where == ls->linenumber) /* all in the same line? */ + error_expected(ls, what); /* do not need a complex message */ else { luaX_syntaxerror(ls, luaO_pushfstring(ls->L, "%s expected (to close %s at line %d)", @@ -148,72 +156,189 @@ static void init_exp (expdesc *e, expkind k, int i) { } -static void codestring (LexState *ls, expdesc *e, TString *s) { - init_exp(e, VK, luaK_stringK(ls->fs, s)); +static void codestring (expdesc *e, TString *s) { + e->f = e->t = NO_JUMP; + e->k = VKSTR; + e->u.strval = s; } -static void checkname (LexState *ls, expdesc *e) { - codestring(ls, e, str_checkname(ls)); +static void codename (LexState *ls, expdesc *e) { + codestring(e, str_checkname(ls)); } -static int registerlocalvar (LexState *ls, TString *varname) { - FuncState *fs = ls->fs; +/* +** Register a new local variable in the active 'Proto' (for debug +** information). +*/ +static int registerlocalvar (LexState *ls, FuncState *fs, TString *varname) { Proto *f = fs->f; int oldsize = f->sizelocvars; - luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, + luaM_growvector(ls->L, f->locvars, fs->ndebugvars, f->sizelocvars, LocVar, SHRT_MAX, "local variables"); - while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; - f->locvars[fs->nlocvars].varname = varname; + while (oldsize < f->sizelocvars) + f->locvars[oldsize++].varname = NULL; + f->locvars[fs->ndebugvars].varname = varname; + f->locvars[fs->ndebugvars].startpc = fs->pc; luaC_objbarrier(ls->L, f, varname); - return fs->nlocvars++; + return fs->ndebugvars++; } -static void new_localvar (LexState *ls, TString *name) { +/* +** Create a new local variable with the given 'name'. Return its index +** in the function. +*/ +static int new_localvar (LexState *ls, TString *name) { + lua_State *L = ls->L; FuncState *fs = ls->fs; Dyndata *dyd = ls->dyd; - int reg = registerlocalvar(ls, name); + Vardesc *var; checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, - MAXVARS, "local variables"); - luaM_growvector(ls->L, dyd->actvar.arr, dyd->actvar.n + 1, - dyd->actvar.size, Vardesc, MAX_INT, "local variables"); - dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg); + MAXVARS, "local variables"); + luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, + dyd->actvar.size, Vardesc, USHRT_MAX, "local variables"); + var = &dyd->actvar.arr[dyd->actvar.n++]; + var->vd.kind = VDKREG; /* default */ + var->vd.name = name; + return dyd->actvar.n - 1 - fs->firstlocal; } +#define new_localvarliteral(ls,v) \ + new_localvar(ls, \ + luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1)); + -static void new_localvarliteral_ (LexState *ls, const char *name, size_t sz) { - new_localvar(ls, luaX_newstring(ls, name, sz)); + +/* +** Return the "variable description" (Vardesc) of a given variable. +** (Unless noted otherwise, all variables are referred to by their +** compiler indices.) +*/ +static Vardesc *getlocalvardesc (FuncState *fs, int vidx) { + return &fs->ls->dyd->actvar.arr[fs->firstlocal + vidx]; } -#define new_localvarliteral(ls,v) \ - new_localvarliteral_(ls, "" v, (sizeof(v)/sizeof(char))-1) + +/* +** Convert 'nvar', a compiler index level, to its corresponding +** register. For that, search for the highest variable below that level +** that is in a register and uses its register index ('ridx') plus one. +*/ +static int reglevel (FuncState *fs, int nvar) { + while (nvar-- > 0) { + Vardesc *vd = getlocalvardesc(fs, nvar); /* get previous variable */ + if (vd->vd.kind != RDKCTC) /* is in a register? */ + return vd->vd.ridx + 1; + } + return 0; /* no variables in registers */ +} + + +/* +** Return the number of variables in the register stack for the given +** function. +*/ +int luaY_nvarstack (FuncState *fs) { + return reglevel(fs, fs->nactvar); +} -static LocVar *getlocvar (FuncState *fs, int i) { - int idx = fs->ls->dyd->actvar.arr[fs->firstlocal + i].idx; - lua_assert(idx < fs->nlocvars); - return &fs->f->locvars[idx]; +/* +** Get the debug-information entry for current variable 'vidx'. +*/ +static LocVar *localdebuginfo (FuncState *fs, int vidx) { + Vardesc *vd = getlocalvardesc(fs, vidx); + if (vd->vd.kind == RDKCTC) + return NULL; /* no debug info. for constants */ + else { + int idx = vd->vd.pidx; + lua_assert(idx < fs->ndebugvars); + return &fs->f->locvars[idx]; + } } +/* +** Create an expression representing variable 'vidx' +*/ +static void init_var (FuncState *fs, expdesc *e, int vidx) { + e->f = e->t = NO_JUMP; + e->k = VLOCAL; + e->u.var.vidx = vidx; + e->u.var.ridx = getlocalvardesc(fs, vidx)->vd.ridx; +} + + +/* +** Raises an error if variable described by 'e' is read only +*/ +static void check_readonly (LexState *ls, expdesc *e) { + FuncState *fs = ls->fs; + TString *varname = NULL; /* to be set if variable is const */ + switch (e->k) { + case VCONST: { + varname = ls->dyd->actvar.arr[e->u.info].vd.name; + break; + } + case VLOCAL: { + Vardesc *vardesc = getlocalvardesc(fs, e->u.var.vidx); + if (vardesc->vd.kind != VDKREG) /* not a regular variable? */ + varname = vardesc->vd.name; + break; + } + case VUPVAL: { + Upvaldesc *up = &fs->f->upvalues[e->u.info]; + if (up->kind != VDKREG) + varname = up->name; + break; + } + default: + return; /* other cases cannot be read-only */ + } + if (varname) { + const char *msg = luaO_pushfstring(ls->L, + "attempt to assign to const variable '%s'", getstr(varname)); + luaK_semerror(ls, msg); /* error */ + } +} + + +/* +** Start the scope for the last 'nvars' created variables. +*/ static void adjustlocalvars (LexState *ls, int nvars) { FuncState *fs = ls->fs; - fs->nactvar = cast_byte(fs->nactvar + nvars); - for (; nvars; nvars--) { - getlocvar(fs, fs->nactvar - nvars)->startpc = fs->pc; + int reglevel = luaY_nvarstack(fs); + int i; + for (i = 0; i < nvars; i++) { + int vidx = fs->nactvar++; + Vardesc *var = getlocalvardesc(fs, vidx); + var->vd.ridx = reglevel++; + var->vd.pidx = registerlocalvar(ls, fs, var->vd.name); } } +/* +** Close the scope for all variables up to level 'tolevel'. +** (debug info.) +*/ static void removevars (FuncState *fs, int tolevel) { fs->ls->dyd->actvar.n -= (fs->nactvar - tolevel); - while (fs->nactvar > tolevel) - getlocvar(fs, --fs->nactvar)->endpc = fs->pc; + while (fs->nactvar > tolevel) { + LocVar *var = localdebuginfo(fs, --fs->nactvar); + if (var) /* does it have debug information? */ + var->endpc = fs->pc; + } } +/* +** Search the upvalues of the function 'fs' for one +** with the given 'name'. +*/ static int searchupvalue (FuncState *fs, TString *name) { int i; Upvaldesc *up = fs->f->upvalues; @@ -224,159 +349,214 @@ static int searchupvalue (FuncState *fs, TString *name) { } -static int newupvalue (FuncState *fs, TString *name, expdesc *v) { +static Upvaldesc *allocupvalue (FuncState *fs) { Proto *f = fs->f; int oldsize = f->sizeupvalues; checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues, Upvaldesc, MAXUPVAL, "upvalues"); - while (oldsize < f->sizeupvalues) f->upvalues[oldsize++].name = NULL; - f->upvalues[fs->nups].instack = (v->k == VLOCAL); - f->upvalues[fs->nups].idx = cast_byte(v->u.info); - f->upvalues[fs->nups].name = name; - luaC_objbarrier(fs->ls->L, f, name); - return fs->nups++; + while (oldsize < f->sizeupvalues) + f->upvalues[oldsize++].name = NULL; + return &f->upvalues[fs->nups++]; } -static int searchvar (FuncState *fs, TString *n) { +static int newupvalue (FuncState *fs, TString *name, expdesc *v) { + Upvaldesc *up = allocupvalue(fs); + FuncState *prev = fs->prev; + if (v->k == VLOCAL) { + up->instack = 1; + up->idx = v->u.var.ridx; + up->kind = getlocalvardesc(prev, v->u.var.vidx)->vd.kind; + lua_assert(eqstr(name, getlocalvardesc(prev, v->u.var.vidx)->vd.name)); + } + else { + up->instack = 0; + up->idx = cast_byte(v->u.info); + up->kind = prev->f->upvalues[v->u.info].kind; + lua_assert(eqstr(name, prev->f->upvalues[v->u.info].name)); + } + up->name = name; + luaC_objbarrier(fs->ls->L, fs->f, name); + return fs->nups - 1; +} + + +/* +** Look for an active local variable with the name 'n' in the +** function 'fs'. If found, initialize 'var' with it and return +** its expression kind; otherwise return -1. +*/ +static int searchvar (FuncState *fs, TString *n, expdesc *var) { int i; for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { - if (eqstr(n, getlocvar(fs, i)->varname)) - return i; + Vardesc *vd = getlocalvardesc(fs, i); + if (eqstr(n, vd->vd.name)) { /* found? */ + if (vd->vd.kind == RDKCTC) /* compile-time constant? */ + init_exp(var, VCONST, fs->firstlocal + i); + else /* real variable */ + init_var(fs, var, i); + return var->k; + } } return -1; /* not found */ } /* - Mark block where variable at given level was defined - (to emit close instructions later). +** Mark block where variable at given level was defined +** (to emit close instructions later). */ static void markupval (FuncState *fs, int level) { BlockCnt *bl = fs->bl; - while (bl->nactvar > level) bl = bl->previous; + while (bl->nactvar > level) + bl = bl->previous; bl->upval = 1; + fs->needclose = 1; } /* - Find variable with given name 'n'. If it is an upvalue, add this - upvalue into all intermediate functions. +** Mark that current block has a to-be-closed variable. */ -static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { +static void marktobeclosed (FuncState *fs) { + BlockCnt *bl = fs->bl; + bl->upval = 1; + bl->insidetbc = 1; + fs->needclose = 1; +} + + +/* +** Find a variable with the given name 'n'. If it is an upvalue, add +** this upvalue into all intermediate functions. If it is a global, set +** 'var' as 'void' as a flag. +*/ +static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) /* no more levels? */ - return VVOID; /* default is global */ + init_exp(var, VVOID, 0); /* default is global */ else { - int v = searchvar(fs, n); /* look up locals at current level */ + int v = searchvar(fs, n, var); /* look up locals at current level */ if (v >= 0) { /* found? */ - init_exp(var, VLOCAL, v); /* variable is local */ - if (!base) - markupval(fs, v); /* local will be used as an upval */ - return VLOCAL; + if (v == VLOCAL && !base) + markupval(fs, var->u.var.vidx); /* local will be used as an upval */ } else { /* not found as local at current level; try upvalues */ int idx = searchupvalue(fs, n); /* try existing upvalues */ if (idx < 0) { /* not found? */ - if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */ - return VVOID; /* not found; is a global */ - /* else was LOCAL or UPVAL */ - idx = newupvalue(fs, n, var); /* will be a new upvalue */ + singlevaraux(fs->prev, n, var, 0); /* try upper levels */ + if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */ + idx = newupvalue(fs, n, var); /* will be a new upvalue */ + else /* it is a global or a constant */ + return; /* don't need to do anything at this level */ } - init_exp(var, VUPVAL, idx); - return VUPVAL; + init_exp(var, VUPVAL, idx); /* new or old upvalue */ } } } +/* +** Find a variable with the given name 'n', handling global variables +** too. +*/ static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; - if (singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */ + singlevaraux(fs, varname, var, 1); + if (var->k == VVOID) { /* global name? */ expdesc key; singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ - lua_assert(var->k == VLOCAL || var->k == VUPVAL); - codestring(ls, &key, varname); /* key is variable name */ + lua_assert(var->k != VVOID); /* this one must exist */ + luaK_exp2anyregup(fs, var); /* but could be a constant */ + codestring(&key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ } } +/* +** Adjust the number of results from an expression list 'e' with 'nexps' +** expressions to 'nvars' values. +*/ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { FuncState *fs = ls->fs; - int extra = nvars - nexps; - if (hasmultret(e->k)) { - extra++; /* includes call itself */ - if (extra < 0) extra = 0; + int needed = nvars - nexps; /* extra values needed */ + if (hasmultret(e->k)) { /* last expression has multiple returns? */ + int extra = needed + 1; /* discount last expression itself */ + if (extra < 0) + extra = 0; luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ - if (extra > 1) luaK_reserveregs(fs, extra-1); } else { - if (e->k != VVOID) luaK_exp2nextreg(fs, e); /* close last expression */ - if (extra > 0) { - int reg = fs->freereg; - luaK_reserveregs(fs, extra); - luaK_nil(fs, reg, extra); - } + if (e->k != VVOID) /* at least one expression? */ + luaK_exp2nextreg(fs, e); /* close last expression */ + if (needed > 0) /* missing values? */ + luaK_nil(fs, fs->freereg, needed); /* complete with nils */ } + if (needed > 0) + luaK_reserveregs(fs, needed); /* registers for extra values */ + else /* adding 'needed' is actually a subtraction */ + fs->freereg += needed; /* remove extra values */ } -static void enterlevel (LexState *ls) { - lua_State *L = ls->L; - ++L->nCcalls; - checklimit(ls->fs, L->nCcalls, LUAI_MAXCCALLS, "C levels"); -} +#define enterlevel(ls) luaE_incCstack(ls->L) + +#define leavelevel(ls) ((ls)->L->nCcalls--) -#define leavelevel(ls) ((ls)->L->nCcalls--) + +/* +** Generates an error that a goto jumps into the scope of some +** local variable. +*/ +static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) { + const char *varname = getstr(getlocalvardesc(ls->fs, gt->nactvar)->vd.name); + const char *msg = " at line %d jumps into the scope of local '%s'"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line, varname); + luaK_semerror(ls, msg); /* raise the error */ +} -static void closegoto (LexState *ls, int g, Labeldesc *label) { +/* +** Solves the goto at index 'g' to given 'label' and removes it +** from the list of pending gotos. +** If it jumps into the scope of some variable, raises an error. +*/ +static void solvegoto (LexState *ls, int g, Labeldesc *label) { int i; - FuncState *fs = ls->fs; - Labellist *gl = &ls->dyd->gt; - Labeldesc *gt = &gl->arr[g]; + Labellist *gl = &ls->dyd->gt; /* list of gotos */ + Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */ lua_assert(eqstr(gt->name, label->name)); - if (gt->nactvar < label->nactvar) { - TString *vname = getlocvar(fs, gt->nactvar)->varname; - const char *msg = luaO_pushfstring(ls->L, - " at line %d jumps into the scope of local '%s'", - getstr(gt->name), gt->line, getstr(vname)); - semerror(ls, msg); - } - luaK_patchlist(fs, gt->pc, label->pc); - /* remove goto from pending list */ - for (i = g; i < gl->n - 1; i++) + if (l_unlikely(gt->nactvar < label->nactvar)) /* enter some scope? */ + jumpscopeerror(ls, gt); + luaK_patchlist(ls->fs, gt->pc, label->pc); + for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */ gl->arr[i] = gl->arr[i + 1]; gl->n--; } /* -** try to close a goto with existing labels; this solves backward jumps +** Search for an active label with the given name. */ -static int findlabel (LexState *ls, int g) { +static Labeldesc *findlabel (LexState *ls, TString *name) { int i; - BlockCnt *bl = ls->fs->bl; Dyndata *dyd = ls->dyd; - Labeldesc *gt = &dyd->gt.arr[g]; - /* check labels in current block for a match */ - for (i = bl->firstlabel; i < dyd->label.n; i++) { + /* check labels in current function for a match */ + for (i = ls->fs->firstlabel; i < dyd->label.n; i++) { Labeldesc *lb = &dyd->label.arr[i]; - if (eqstr(lb->name, gt->name)) { /* correct label? */ - if (gt->nactvar > lb->nactvar && - (bl->upval || dyd->label.n > bl->firstlabel)) - luaK_patchclose(ls->fs, gt->pc, lb->nactvar); - closegoto(ls, g, lb); /* close it */ - return 1; - } + if (eqstr(lb->name, name)) /* correct label? */ + return lb; } - return 0; /* label not found; cannot close goto */ + return NULL; /* label not found */ } +/* +** Adds a new label/goto in the corresponding list. +*/ static int newlabelentry (LexState *ls, Labellist *l, TString *name, int line, int pc) { int n = l->n; @@ -385,48 +565,76 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name, l->arr[n].name = name; l->arr[n].line = line; l->arr[n].nactvar = ls->fs->nactvar; + l->arr[n].close = 0; l->arr[n].pc = pc; l->n = n + 1; return n; } +static int newgotoentry (LexState *ls, TString *name, int line, int pc) { + return newlabelentry(ls, &ls->dyd->gt, name, line, pc); +} + + /* -** check whether new label 'lb' matches any pending gotos in current -** block; solves forward jumps +** Solves forward jumps. Check whether new label 'lb' matches any +** pending gotos in current block and solves them. Return true +** if any of the gotos need to close upvalues. */ -static void findgotos (LexState *ls, Labeldesc *lb) { +static int solvegotos (LexState *ls, Labeldesc *lb) { Labellist *gl = &ls->dyd->gt; int i = ls->fs->bl->firstgoto; + int needsclose = 0; while (i < gl->n) { - if (eqstr(gl->arr[i].name, lb->name)) - closegoto(ls, i, lb); + if (eqstr(gl->arr[i].name, lb->name)) { + needsclose |= gl->arr[i].close; + solvegoto(ls, i, lb); /* will remove 'i' from the list */ + } else i++; } + return needsclose; } /* -** export pending gotos to outer level, to check them against -** outer labels; if the block being exited has upvalues, and -** the goto exits the scope of any variable (which can be the -** upvalue), close those variables being exited. +** Create a new label with the given 'name' at the given 'line'. +** 'last' tells whether label is the last non-op statement in its +** block. Solves all pending gotos to this new label and adds +** a close instruction if necessary. +** Returns true iff it added a close instruction. +*/ +static int createlabel (LexState *ls, TString *name, int line, + int last) { + FuncState *fs = ls->fs; + Labellist *ll = &ls->dyd->label; + int l = newlabelentry(ls, ll, name, line, luaK_getlabel(fs)); + if (last) { /* label is last no-op statement in the block? */ + /* assume that locals are already out of scope */ + ll->arr[l].nactvar = fs->bl->nactvar; + } + if (solvegotos(ls, &ll->arr[l])) { /* need close? */ + luaK_codeABC(fs, OP_CLOSE, luaY_nvarstack(fs), 0, 0); + return 1; + } + return 0; +} + + +/* +** Adjust pending gotos to outer level of a block. */ static void movegotosout (FuncState *fs, BlockCnt *bl) { - int i = bl->firstgoto; + int i; Labellist *gl = &fs->ls->dyd->gt; - /* correct pending gotos to current block and try to close it - with visible labels */ - while (i < gl->n) { + /* correct pending gotos to current block */ + for (i = bl->firstgoto; i < gl->n; i++) { /* for each pending goto */ Labeldesc *gt = &gl->arr[i]; - if (gt->nactvar > bl->nactvar) { - if (bl->upval) - luaK_patchclose(fs, gt->pc, bl->nactvar); - gt->nactvar = bl->nactvar; - } - if (!findlabel(fs->ls, i)) - i++; /* move to next one */ + /* leaving a variable scope? */ + if (reglevel(fs, gt->nactvar) > reglevel(fs, bl->nactvar)) + gt->close |= bl->upval; /* jump may need a close */ + gt->nactvar = bl->nactvar; /* update goto level */ } } @@ -437,54 +645,50 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { bl->firstlabel = fs->ls->dyd->label.n; bl->firstgoto = fs->ls->dyd->gt.n; bl->upval = 0; + bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); bl->previous = fs->bl; fs->bl = bl; - lua_assert(fs->freereg == fs->nactvar); + lua_assert(fs->freereg == luaY_nvarstack(fs)); } /* -** create a label named 'break' to resolve break statements -*/ -static void breaklabel (LexState *ls) { - TString *n = luaS_new(ls->L, "break"); - int l = newlabelentry(ls, &ls->dyd->label, n, 0, ls->fs->pc); - findgotos(ls, &ls->dyd->label.arr[l]); -} - -/* -** generates an error for an undefined 'goto'; choose appropriate -** message when label name is a reserved word (which can only be 'break') +** generates an error for an undefined 'goto'. */ static l_noret undefgoto (LexState *ls, Labeldesc *gt) { - const char *msg = isreserved(gt->name) - ? "<%s> at line %d not inside a loop" - : "no visible label '%s' for at line %d"; - msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); - semerror(ls, msg); + const char *msg; + if (eqstr(gt->name, luaS_newliteral(ls->L, "break"))) { + msg = "break outside loop at line %d"; + msg = luaO_pushfstring(ls->L, msg, gt->line); + } + else { + msg = "no visible label '%s' for at line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); + } + luaK_semerror(ls, msg); } static void leaveblock (FuncState *fs) { BlockCnt *bl = fs->bl; LexState *ls = fs->ls; - if (bl->previous && bl->upval) { - /* create a 'jump to here' to close upvalues */ - int j = luaK_jump(fs); - luaK_patchclose(fs, j, bl->nactvar); - luaK_patchtohere(fs, j); - } - if (bl->isloop) - breaklabel(ls); /* close pending breaks */ - fs->bl = bl->previous; - removevars(fs, bl->nactvar); - lua_assert(bl->nactvar == fs->nactvar); - fs->freereg = fs->nactvar; /* free registers */ + int hasclose = 0; + int stklevel = reglevel(fs, bl->nactvar); /* level outside the block */ + removevars(fs, bl->nactvar); /* remove block locals */ + lua_assert(bl->nactvar == fs->nactvar); /* back to level on entry */ + if (bl->isloop) /* has to fix pending breaks? */ + hasclose = createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); + if (!hasclose && bl->previous && bl->upval) /* still need a 'close'? */ + luaK_codeABC(fs, OP_CLOSE, stklevel, 0, 0); + fs->freereg = stklevel; /* free registers */ ls->dyd->label.n = bl->firstlabel; /* remove local labels */ - if (bl->previous) /* inner block? */ - movegotosout(fs, bl); /* update pending gotos to outer block */ - else if (bl->firstgoto < ls->dyd->gt.n) /* pending gotos in outer block? */ - undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ + fs->bl = bl->previous; /* current block now is previous one */ + if (bl->previous) /* was it a nested block? */ + movegotosout(fs, bl); /* update pending gotos to enclosing block */ + else { + if (bl->firstgoto < ls->dyd->gt.n) /* still pending gotos? */ + undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ + } } @@ -499,7 +703,8 @@ static Proto *addprototype (LexState *ls) { if (fs->np >= f->sizep) { int oldsize = f->sizep; luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "functions"); - while (oldsize < f->sizep) f->p[oldsize++] = NULL; + while (oldsize < f->sizep) + f->p[oldsize++] = NULL; } f->p[fs->np++] = clp = luaF_newproto(L); luaC_objbarrier(L, f, clp); @@ -509,35 +714,40 @@ static Proto *addprototype (LexState *ls) { /* ** codes instruction to create new closure in parent function. -** The OP_CLOSURE instruction must use the last available register, +** The OP_CLOSURE instruction uses the last available register, ** so that, if it invokes the GC, the GC knows which registers ** are in use at that time. + */ static void codeclosure (LexState *ls, expdesc *v) { FuncState *fs = ls->fs->prev; - init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1)); + init_exp(v, VRELOC, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1)); luaK_exp2nextreg(fs, v); /* fix it at the last register */ } static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { - Proto *f; + Proto *f = fs->f; fs->prev = ls->fs; /* linked list of funcstates */ fs->ls = ls; ls->fs = fs; fs->pc = 0; + fs->previousline = f->linedefined; + fs->iwthabs = 0; fs->lasttarget = 0; - fs->jpc = NO_JUMP; fs->freereg = 0; fs->nk = 0; + fs->nabslineinfo = 0; fs->np = 0; fs->nups = 0; - fs->nlocvars = 0; + fs->ndebugvars = 0; fs->nactvar = 0; + fs->needclose = 0; fs->firstlocal = ls->dyd->actvar.n; + fs->firstlabel = ls->dyd->label.n; fs->bl = NULL; - f = fs->f; f->source = ls->source; + luaC_objbarrier(ls->L, f, f->source); f->maxstacksize = 2; /* registers 0/1 are always valid */ enterblock(fs, bl, 0); } @@ -547,21 +757,18 @@ static void close_func (LexState *ls) { lua_State *L = ls->L; FuncState *fs = ls->fs; Proto *f = fs->f; - luaK_ret(fs, 0, 0); /* final return */ + luaK_ret(fs, luaY_nvarstack(fs), 0); /* final return */ leaveblock(fs); - luaM_reallocvector(L, f->code, f->sizecode, fs->pc, Instruction); - f->sizecode = fs->pc; - luaM_reallocvector(L, f->lineinfo, f->sizelineinfo, fs->pc, int); - f->sizelineinfo = fs->pc; - luaM_reallocvector(L, f->k, f->sizek, fs->nk, TValue); - f->sizek = fs->nk; - luaM_reallocvector(L, f->p, f->sizep, fs->np, Proto *); - f->sizep = fs->np; - luaM_reallocvector(L, f->locvars, f->sizelocvars, fs->nlocvars, LocVar); - f->sizelocvars = fs->nlocvars; - luaM_reallocvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc); - f->sizeupvalues = fs->nups; lua_assert(fs->bl == NULL); + luaK_finish(fs); + luaM_shrinkvector(L, f->code, f->sizecode, fs->pc, Instruction); + luaM_shrinkvector(L, f->lineinfo, f->sizelineinfo, fs->pc, ls_byte); + luaM_shrinkvector(L, f->abslineinfo, f->sizeabslineinfo, + fs->nabslineinfo, AbsLineInfo); + luaM_shrinkvector(L, f->k, f->sizek, fs->nk, TValue); + luaM_shrinkvector(L, f->p, f->sizep, fs->np, Proto *); + luaM_shrinkvector(L, f->locvars, f->sizelocvars, fs->ndebugvars, LocVar); + luaM_shrinkvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc); ls->fs = fs->prev; luaC_checkGC(L); } @@ -607,7 +814,7 @@ static void fieldsel (LexState *ls, expdesc *v) { expdesc key; luaK_exp2anyregup(fs, v); luaX_next(ls); /* skip the dot or colon */ - checkname(ls, &key); + codename(ls, &key); luaK_indexed(fs, v, &key); } @@ -628,48 +835,49 @@ static void yindex (LexState *ls, expdesc *v) { */ -struct ConsControl { +typedef struct ConsControl { expdesc v; /* last list item read */ expdesc *t; /* table descriptor */ int nh; /* total number of 'record' elements */ - int na; /* total number of array elements */ + int na; /* number of array elements already stored */ int tostore; /* number of array elements pending to be stored */ -}; +} ConsControl; -static void recfield (LexState *ls, struct ConsControl *cc) { - /* recfield -> (NAME | '['exp1']') = exp1 */ +static void recfield (LexState *ls, ConsControl *cc) { + /* recfield -> (NAME | '['exp']') = exp */ FuncState *fs = ls->fs; int reg = ls->fs->freereg; - expdesc key, val; - int rkkey; + expdesc tab, key, val; if (ls->t.token == TK_NAME) { checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); - checkname(ls, &key); + codename(ls, &key); } else /* ls->t.token == '[' */ yindex(ls, &key); cc->nh++; checknext(ls, '='); - rkkey = luaK_exp2RK(fs, &key); + tab = *cc->t; + luaK_indexed(fs, &tab, &key); expr(ls, &val); - luaK_codeABC(fs, OP_SETTABLE, cc->t->u.info, rkkey, luaK_exp2RK(fs, &val)); + luaK_storevar(fs, &tab, &val); fs->freereg = reg; /* free registers */ } -static void closelistfield (FuncState *fs, struct ConsControl *cc) { +static void closelistfield (FuncState *fs, ConsControl *cc) { if (cc->v.k == VVOID) return; /* there is no list item */ luaK_exp2nextreg(fs, &cc->v); cc->v.k = VVOID; if (cc->tostore == LFIELDS_PER_FLUSH) { luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */ + cc->na += cc->tostore; cc->tostore = 0; /* no more items pending */ } } -static void lastlistfield (FuncState *fs, struct ConsControl *cc) { +static void lastlistfield (FuncState *fs, ConsControl *cc) { if (cc->tostore == 0) return; if (hasmultret(cc->v.k)) { luaK_setmultret(fs, &cc->v); @@ -681,19 +889,18 @@ static void lastlistfield (FuncState *fs, struct ConsControl *cc) { luaK_exp2nextreg(fs, &cc->v); luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); } + cc->na += cc->tostore; } -static void listfield (LexState *ls, struct ConsControl *cc) { +static void listfield (LexState *ls, ConsControl *cc) { /* listfield -> exp */ expr(ls, &cc->v); - checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); - cc->na++; cc->tostore++; } -static void field (LexState *ls, struct ConsControl *cc) { +static void field (LexState *ls, ConsControl *cc) { /* field -> listfield | recfield */ switch(ls->t.token) { case TK_NAME: { /* may be 'listfield' or 'recfield' */ @@ -721,12 +928,13 @@ static void constructor (LexState *ls, expdesc *t) { FuncState *fs = ls->fs; int line = ls->linenumber; int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); - struct ConsControl cc; + ConsControl cc; + luaK_code(fs, 0); /* space for extra arg. */ cc.na = cc.nh = cc.tostore = 0; cc.t = t; - init_exp(t, VRELOCABLE, pc); + init_exp(t, VNONRELOC, fs->freereg); /* table will be at stack top */ + luaK_reserveregs(fs, 1); init_exp(&cc.v, VVOID, 0); /* no value (yet) */ - luaK_exp2nextreg(ls->fs, t); /* fix it at stack top */ checknext(ls, '{'); do { lua_assert(cc.v.k == VVOID || cc.tostore > 0); @@ -736,40 +944,46 @@ static void constructor (LexState *ls, expdesc *t) { } while (testnext(ls, ',') || testnext(ls, ';')); check_match(ls, '}', '{', line); lastlistfield(fs, &cc); - SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */ - SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */ + luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh); } /* }====================================================================== */ +static void setvararg (FuncState *fs, int nparams) { + fs->f->is_vararg = 1; + luaK_codeABC(fs, OP_VARARGPREP, nparams, 0, 0); +} + static void parlist (LexState *ls) { - /* parlist -> [ param { ',' param } ] */ + /* parlist -> [ {NAME ','} (NAME | '...') ] */ FuncState *fs = ls->fs; Proto *f = fs->f; int nparams = 0; - f->is_vararg = 0; + int isvararg = 0; if (ls->t.token != ')') { /* is 'parlist' not empty? */ do { switch (ls->t.token) { - case TK_NAME: { /* param -> NAME */ + case TK_NAME: { new_localvar(ls, str_checkname(ls)); nparams++; break; } - case TK_DOTS: { /* param -> '...' */ + case TK_DOTS: { luaX_next(ls); - f->is_vararg = 1; + isvararg = 1; break; } default: luaX_syntaxerror(ls, " or '...' expected"); } - } while (!f->is_vararg && testnext(ls, ',')); + } while (!isvararg && testnext(ls, ',')); } adjustlocalvars(ls, nparams); f->numparams = cast_byte(fs->nactvar); - luaK_reserveregs(fs, fs->nactvar); /* reserve register for parameters */ + if (isvararg) + setvararg(fs, f->numparams); /* declared vararg */ + luaK_reserveregs(fs, fs->nactvar); /* reserve registers for parameters */ } @@ -808,10 +1022,11 @@ static int explist (LexState *ls, expdesc *v) { } -static void funcargs (LexState *ls, expdesc *f, int line) { +static void funcargs (LexState *ls, expdesc *f) { FuncState *fs = ls->fs; expdesc args; int base, nparams; + int line = ls->linenumber; switch (ls->t.token) { case '(': { /* funcargs -> '(' [ explist ] ')' */ luaX_next(ls); @@ -819,7 +1034,8 @@ static void funcargs (LexState *ls, expdesc *f, int line) { args.k = VVOID; else { explist(ls, &args); - luaK_setmultret(fs, &args); + if (hasmultret(args.k)) + luaK_setmultret(fs, &args); } check_match(ls, ')', '(', line); break; @@ -829,7 +1045,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { break; } case TK_STRING: { /* funcargs -> STRING */ - codestring(ls, &args, ls->t.seminfo.ts); + codestring(&args, ls->t.seminfo.ts); luaX_next(ls); /* must use 'seminfo' before 'next' */ break; } @@ -848,8 +1064,8 @@ static void funcargs (LexState *ls, expdesc *f, int line) { } init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); luaK_fixline(fs, line); - fs->freereg = base+1; /* call remove function and arguments and leaves - (unless changed) one result */ + fs->freereg = base+1; /* call removes function and arguments and leaves + one result (unless changed later) */ } @@ -888,7 +1104,6 @@ static void suffixedexp (LexState *ls, expdesc *v) { /* suffixedexp -> primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ FuncState *fs = ls->fs; - int line = ls->linenumber; primaryexp(ls, v); for (;;) { switch (ls->t.token) { @@ -896,7 +1111,7 @@ static void suffixedexp (LexState *ls, expdesc *v) { fieldsel(ls, v); break; } - case '[': { /* '[' exp1 ']' */ + case '[': { /* '[' exp ']' */ expdesc key; luaK_exp2anyregup(fs, v); yindex(ls, &key); @@ -906,14 +1121,14 @@ static void suffixedexp (LexState *ls, expdesc *v) { case ':': { /* ':' NAME funcargs */ expdesc key; luaX_next(ls); - checkname(ls, &key); + codename(ls, &key); luaK_self(fs, v, &key); - funcargs(ls, v, line); + funcargs(ls, v); break; } case '(': case TK_STRING: case '{': { /* funcargs */ luaK_exp2nextreg(fs, v); - funcargs(ls, v, line); + funcargs(ls, v); break; } default: return; @@ -937,7 +1152,7 @@ static void simpleexp (LexState *ls, expdesc *v) { break; } case TK_STRING: { - codestring(ls, v, ls->t.seminfo.ts); + codestring(v, ls->t.seminfo.ts); break; } case TK_NIL: { @@ -956,7 +1171,7 @@ static void simpleexp (LexState *ls, expdesc *v) { FuncState *fs = ls->fs; check_condition(ls, fs->f->is_vararg, "cannot use '...' outside a vararg function"); - init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0)); + init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 0, 1)); break; } case '{': { /* constructor */ @@ -1016,6 +1231,9 @@ static BinOpr getbinopr (int op) { } +/* +** Priority table for binary operators. +*/ static const struct { lu_byte left; /* left priority for each binary operator */ lu_byte right; /* right priority */ @@ -1044,9 +1262,9 @@ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { UnOpr uop; enterlevel(ls); uop = getunopr(ls->t.token); - if (uop != OPR_NOUNOPR) { + if (uop != OPR_NOUNOPR) { /* prefix (unary) operator? */ int line = ls->linenumber; - luaX_next(ls); + luaX_next(ls); /* skip operator */ subexpr(ls, v, UNARY_PRIORITY); luaK_prefix(ls->fs, uop, v, line); } @@ -1057,7 +1275,7 @@ static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { expdesc v2; BinOpr nextop; int line = ls->linenumber; - luaX_next(ls); + luaX_next(ls); /* skip operator */ luaK_infix(ls->fs, op, v); /* read sub-expression with higher priority */ nextop = subexpr(ls, &v2, priority[op].right); @@ -1115,51 +1333,65 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { int extra = fs->freereg; /* eventual position to save local variable */ int conflict = 0; for (; lh; lh = lh->prev) { /* check all previous assignments */ - if (lh->v.k == VINDEXED) { /* assigning to a table? */ - /* table is the upvalue/local being assigned now? */ - if (lh->v.u.ind.vt == v->k && lh->v.u.ind.t == v->u.info) { - conflict = 1; - lh->v.u.ind.vt = VLOCAL; - lh->v.u.ind.t = extra; /* previous assignment will use safe copy */ + if (vkisindexed(lh->v.k)) { /* assignment to table field? */ + if (lh->v.k == VINDEXUP) { /* is table an upvalue? */ + if (v->k == VUPVAL && lh->v.u.ind.t == v->u.info) { + conflict = 1; /* table is the upvalue being assigned now */ + lh->v.k = VINDEXSTR; + lh->v.u.ind.t = extra; /* assignment will use safe copy */ + } } - /* index is the local being assigned? (index cannot be upvalue) */ - if (v->k == VLOCAL && lh->v.u.ind.idx == v->u.info) { - conflict = 1; - lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */ + else { /* table is a register */ + if (v->k == VLOCAL && lh->v.u.ind.t == v->u.var.ridx) { + conflict = 1; /* table is the local being assigned now */ + lh->v.u.ind.t = extra; /* assignment will use safe copy */ + } + /* is index the local being assigned? */ + if (lh->v.k == VINDEXED && v->k == VLOCAL && + lh->v.u.ind.idx == v->u.var.ridx) { + conflict = 1; + lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */ + } } } } if (conflict) { /* copy upvalue/local value to a temporary (in position 'extra') */ - OpCode op = (v->k == VLOCAL) ? OP_MOVE : OP_GETUPVAL; - luaK_codeABC(fs, op, extra, v->u.info, 0); + if (v->k == VLOCAL) + luaK_codeABC(fs, OP_MOVE, extra, v->u.var.ridx, 0); + else + luaK_codeABC(fs, OP_GETUPVAL, extra, v->u.info, 0); luaK_reserveregs(fs, 1); } } - -static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { +/* +** Parse and compile a multiple assignment. The first "variable" +** (a 'suffixedexp') was already read by the caller. +** +** assignment -> suffixedexp restassign +** restassign -> ',' suffixedexp restassign | '=' explist +*/ +static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) { expdesc e; check_condition(ls, vkisvar(lh->v.k), "syntax error"); - if (testnext(ls, ',')) { /* assignment -> ',' suffixedexp assignment */ + check_readonly(ls, &lh->v); + if (testnext(ls, ',')) { /* restassign -> ',' suffixedexp restassign */ struct LHS_assign nv; nv.prev = lh; suffixedexp(ls, &nv.v); - if (nv.v.k != VINDEXED) + if (!vkisindexed(nv.v.k)) check_conflict(ls, lh, &nv.v); - checklimit(ls->fs, nvars + ls->L->nCcalls, LUAI_MAXCCALLS, - "C levels"); - assignment(ls, &nv, nvars+1); + enterlevel(ls); /* control recursion depth */ + restassign(ls, &nv, nvars+1); + leavelevel(ls); } - else { /* assignment -> '=' explist */ + else { /* restassign -> '=' explist */ int nexps; checknext(ls, '='); nexps = explist(ls, &e); - if (nexps != nvars) { + if (nexps != nvars) adjust_assign(ls, nvars, nexps, &e); - if (nexps > nvars) - ls->fs->freereg -= nexps - nvars; /* remove extra values */ - } else { luaK_setoneret(ls->fs, &e); /* close last expression */ luaK_storevar(ls->fs, &lh->v, &e); @@ -1181,57 +1413,55 @@ static int cond (LexState *ls) { } -static void gotostat (LexState *ls, int pc) { +static void gotostat (LexState *ls) { + FuncState *fs = ls->fs; int line = ls->linenumber; - TString *label; - int g; - if (testnext(ls, TK_GOTO)) - label = str_checkname(ls); - else { - luaX_next(ls); /* skip break */ - label = luaS_new(ls->L, "break"); + TString *name = str_checkname(ls); /* label's name */ + Labeldesc *lb = findlabel(ls, name); + if (lb == NULL) /* no label? */ + /* forward jump; will be resolved when the label is declared */ + newgotoentry(ls, name, line, luaK_jump(fs)); + else { /* found a label */ + /* backward jump; will be resolved here */ + int lblevel = reglevel(fs, lb->nactvar); /* label level */ + if (luaY_nvarstack(fs) > lblevel) /* leaving the scope of a variable? */ + luaK_codeABC(fs, OP_CLOSE, lblevel, 0, 0); + /* create jump and link it to the label */ + luaK_patchlist(fs, luaK_jump(fs), lb->pc); } - g = newlabelentry(ls, &ls->dyd->gt, label, line, pc); - findlabel(ls, g); /* close it if label already defined */ } -/* check for repeated labels on the same block */ -static void checkrepeated (FuncState *fs, Labellist *ll, TString *label) { - int i; - for (i = fs->bl->firstlabel; i < ll->n; i++) { - if (eqstr(label, ll->arr[i].name)) { - const char *msg = luaO_pushfstring(fs->ls->L, - "label '%s' already defined on line %d", - getstr(label), ll->arr[i].line); - semerror(fs->ls, msg); - } - } +/* +** Break statement. Semantically equivalent to "goto break". +*/ +static void breakstat (LexState *ls) { + int line = ls->linenumber; + luaX_next(ls); /* skip break */ + newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, luaK_jump(ls->fs)); } -/* skip no-op statements */ -static void skipnoopstat (LexState *ls) { - while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) - statement(ls); +/* +** Check whether there is already a label with the given 'name'. +*/ +static void checkrepeated (LexState *ls, TString *name) { + Labeldesc *lb = findlabel(ls, name); + if (l_unlikely(lb != NULL)) { /* already defined? */ + const char *msg = "label '%s' already defined on line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); + luaK_semerror(ls, msg); /* error */ + } } -static void labelstat (LexState *ls, TString *label, int line) { +static void labelstat (LexState *ls, TString *name, int line) { /* label -> '::' NAME '::' */ - FuncState *fs = ls->fs; - Labellist *ll = &ls->dyd->label; - int l; /* index of new label being created */ - checkrepeated(fs, ll, label); /* check for repeated labels */ checknext(ls, TK_DBCOLON); /* skip double colon */ - /* create new entry for this label */ - l = newlabelentry(ls, ll, label, line, fs->pc); - skipnoopstat(ls); /* skip other no-op statements */ - if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ - /* assume that locals are already out of scope */ - ll->arr[l].nactvar = fs->bl->nactvar; - } - findgotos(ls, &ll->arr[l]); + while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) + statement(ls); /* skip other no-op statements */ + checkrepeated(ls, name); /* check for repeated labels */ + createlabel(ls, name, line, block_follow(ls, 0)); } @@ -1266,58 +1496,83 @@ static void repeatstat (LexState *ls, int line) { statlist(ls); check_match(ls, TK_UNTIL, TK_REPEAT, line); condexit = cond(ls); /* read condition (inside scope block) */ - if (bl2.upval) /* upvalues? */ - luaK_patchclose(fs, condexit, bl2.nactvar); leaveblock(fs); /* finish scope */ + if (bl2.upval) { /* upvalues? */ + int exit = luaK_jump(fs); /* normal exit must jump over fix */ + luaK_patchtohere(fs, condexit); /* repetition must close upvalues */ + luaK_codeABC(fs, OP_CLOSE, reglevel(fs, bl2.nactvar), 0, 0); + condexit = luaK_jump(fs); /* repeat after closing upvalues */ + luaK_patchtohere(fs, exit); /* normal exit comes to here */ + } luaK_patchlist(fs, condexit, repeat_init); /* close the loop */ leaveblock(fs); /* finish loop */ } -static int exp1 (LexState *ls) { +/* +** Read an expression and generate code to put its results in next +** stack slot. +** +*/ +static void exp1 (LexState *ls) { expdesc e; - int reg; expr(ls, &e); luaK_exp2nextreg(ls->fs, &e); lua_assert(e.k == VNONRELOC); - reg = e.u.info; - return reg; } -static void forbody (LexState *ls, int base, int line, int nvars, int isnum) { +/* +** Fix for instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua). 'back' true means +** a back jump. +*/ +static void fixforjump (FuncState *fs, int pc, int dest, int back) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + if (back) + offset = -offset; + if (l_unlikely(offset > MAXARG_Bx)) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_Bx(*jmp, offset); +} + + +/* +** Generate code for a 'for' loop. +*/ +static void forbody (LexState *ls, int base, int line, int nvars, int isgen) { /* forbody -> DO block */ + static const OpCode forprep[2] = {OP_FORPREP, OP_TFORPREP}; + static const OpCode forloop[2] = {OP_FORLOOP, OP_TFORLOOP}; BlockCnt bl; FuncState *fs = ls->fs; int prep, endfor; - adjustlocalvars(ls, 3); /* control variables */ checknext(ls, TK_DO); - prep = isnum ? luaK_codeAsBx(fs, OP_FORPREP, base, NO_JUMP) : luaK_jump(fs); + prep = luaK_codeABx(fs, forprep[isgen], base, 0); enterblock(fs, &bl, 0); /* scope for declared variables */ adjustlocalvars(ls, nvars); luaK_reserveregs(fs, nvars); block(ls); leaveblock(fs); /* end of scope for declared variables */ - luaK_patchtohere(fs, prep); - if (isnum) /* numeric for? */ - endfor = luaK_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP); - else { /* generic for */ + fixforjump(fs, prep, luaK_getlabel(fs), 0); + if (isgen) { /* generic for? */ luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars); luaK_fixline(fs, line); - endfor = luaK_codeAsBx(fs, OP_TFORLOOP, base + 2, NO_JUMP); } - luaK_patchlist(fs, endfor, prep + 1); + endfor = luaK_codeABx(fs, forloop[isgen], base, 0); + fixforjump(fs, endfor, prep + 1, 1); luaK_fixline(fs, line); } static void fornum (LexState *ls, TString *varname, int line) { - /* fornum -> NAME = exp1,exp1[,exp1] forbody */ + /* fornum -> NAME = exp,exp[,exp] forbody */ FuncState *fs = ls->fs; int base = fs->freereg; - new_localvarliteral(ls, "(for index)"); - new_localvarliteral(ls, "(for limit)"); - new_localvarliteral(ls, "(for step)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); new_localvar(ls, varname); checknext(ls, '='); exp1(ls); /* initial value */ @@ -1326,10 +1581,11 @@ static void fornum (LexState *ls, TString *varname, int line) { if (testnext(ls, ',')) exp1(ls); /* optional step */ else { /* default step = 1 */ - luaK_codek(fs, fs->freereg, luaK_intK(fs, 1)); + luaK_int(fs, fs->freereg, 1); luaK_reserveregs(fs, 1); } - forbody(ls, base, line, 1, 1); + adjustlocalvars(ls, 3); /* control variables */ + forbody(ls, base, line, 1, 0); } @@ -1337,13 +1593,14 @@ static void forlist (LexState *ls, TString *indexname) { /* forlist -> NAME {,NAME} IN explist forbody */ FuncState *fs = ls->fs; expdesc e; - int nvars = 4; /* gen, state, control, plus at least one declared var */ + int nvars = 5; /* gen, state, control, toclose, 'indexname' */ int line; int base = fs->freereg; /* create control variables */ - new_localvarliteral(ls, "(for generator)"); new_localvarliteral(ls, "(for state)"); - new_localvarliteral(ls, "(for control)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); /* create declared variables */ new_localvar(ls, indexname); while (testnext(ls, ',')) { @@ -1352,9 +1609,11 @@ static void forlist (LexState *ls, TString *indexname) { } checknext(ls, TK_IN); line = ls->linenumber; - adjust_assign(ls, 3, explist(ls, &e), &e); + adjust_assign(ls, 4, explist(ls, &e), &e); + adjustlocalvars(ls, 4); /* control variables */ + marktobeclosed(fs); /* last control var. must be closed */ luaK_checkstack(fs, 3); /* extra space to call generator */ - forbody(ls, base, line, nvars - 3, 0); + forbody(ls, base, line, nvars - 4, 1); } @@ -1385,19 +1644,21 @@ static void test_then_block (LexState *ls, int *escapelist) { luaX_next(ls); /* skip IF or ELSEIF */ expr(ls, &v); /* read condition */ checknext(ls, TK_THEN); - if (ls->t.token == TK_GOTO || ls->t.token == TK_BREAK) { - luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ + if (ls->t.token == TK_BREAK) { /* 'if x then break' ? */ + int line = ls->linenumber; + luaK_goiffalse(ls->fs, &v); /* will jump if condition is true */ + luaX_next(ls); /* skip 'break' */ enterblock(fs, &bl, 0); /* must enter block before 'goto' */ - gotostat(ls, v.t); /* handle goto/break */ - skipnoopstat(ls); /* skip other no-op statements */ - if (block_follow(ls, 0)) { /* 'goto' is the entire block? */ + newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, v.t); + while (testnext(ls, ';')) {} /* skip semicolons */ + if (block_follow(ls, 0)) { /* jump is the entire block? */ leaveblock(fs); return; /* and that is it */ } else /* must skip over 'then' part if condition is false */ jf = luaK_jump(fs); } - else { /* regular case (not goto/break) */ + else { /* regular case (not a break) */ luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */ enterblock(fs, &bl, 0); jf = v.f; @@ -1428,21 +1689,58 @@ static void ifstat (LexState *ls, int line) { static void localfunc (LexState *ls) { expdesc b; FuncState *fs = ls->fs; + int fvar = fs->nactvar; /* function's variable index */ new_localvar(ls, str_checkname(ls)); /* new local variable */ adjustlocalvars(ls, 1); /* enter its scope */ body(ls, &b, 0, ls->linenumber); /* function created in next register */ /* debug information will only see the variable after this point! */ - getlocvar(fs, b.u.info)->startpc = fs->pc; + localdebuginfo(fs, fvar)->startpc = fs->pc; +} + + +static int getlocalattribute (LexState *ls) { + /* ATTRIB -> ['<' Name '>'] */ + if (testnext(ls, '<')) { + const char *attr = getstr(str_checkname(ls)); + checknext(ls, '>'); + if (strcmp(attr, "const") == 0) + return RDKCONST; /* read-only variable */ + else if (strcmp(attr, "close") == 0) + return RDKTOCLOSE; /* to-be-closed variable */ + else + luaK_semerror(ls, + luaO_pushfstring(ls->L, "unknown attribute '%s'", attr)); + } + return VDKREG; /* regular variable */ +} + + +static void checktoclose (FuncState *fs, int level) { + if (level != -1) { /* is there a to-be-closed variable? */ + marktobeclosed(fs); + luaK_codeABC(fs, OP_TBC, reglevel(fs, level), 0, 0); + } } static void localstat (LexState *ls) { - /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ + /* stat -> LOCAL NAME ATTRIB { ',' NAME ATTRIB } ['=' explist] */ + FuncState *fs = ls->fs; + int toclose = -1; /* index of to-be-closed variable (if any) */ + Vardesc *var; /* last variable */ + int vidx, kind; /* index and kind of last variable */ int nvars = 0; int nexps; expdesc e; do { - new_localvar(ls, str_checkname(ls)); + vidx = new_localvar(ls, str_checkname(ls)); + kind = getlocalattribute(ls); + getlocalvardesc(fs, vidx)->vd.kind = kind; + if (kind == RDKTOCLOSE) { /* to-be-closed? */ + if (toclose != -1) /* one already present? */ + luaK_semerror(ls, "multiple to-be-closed variables in local list"); + toclose = fs->nactvar + nvars; + } nvars++; } while (testnext(ls, ',')); if (testnext(ls, '=')) @@ -1451,8 +1749,19 @@ static void localstat (LexState *ls) { e.k = VVOID; nexps = 0; } - adjust_assign(ls, nvars, nexps, &e); - adjustlocalvars(ls, nvars); + var = getlocalvardesc(fs, vidx); /* get last variable */ + if (nvars == nexps && /* no adjustments? */ + var->vd.kind == RDKCONST && /* last variable is const? */ + luaK_exp2const(fs, &e, &var->k)) { /* compile-time constant? */ + var->vd.kind = RDKCTC; /* variable is a compile-time constant */ + adjustlocalvars(ls, nvars - 1); /* exclude last variable */ + fs->nactvar++; /* but count it */ + } + else { + adjust_assign(ls, nvars, nexps, &e); + adjustlocalvars(ls, nvars); + } + checktoclose(fs, toclose); } @@ -1477,6 +1786,7 @@ static void funcstat (LexState *ls, int line) { luaX_next(ls); /* skip FUNCTION */ ismethod = funcname(ls, &v); body(ls, &b, ismethod, line); + check_readonly(ls, &v); luaK_storevar(ls->fs, &v, &b); luaK_fixline(ls->fs, line); /* definition "happens" in the first line */ } @@ -1489,11 +1799,13 @@ static void exprstat (LexState *ls) { suffixedexp(ls, &v.v); if (ls->t.token == '=' || ls->t.token == ',') { /* stat -> assignment ? */ v.prev = NULL; - assignment(ls, &v, 1); + restassign(ls, &v, 1); } else { /* stat -> func */ + Instruction *inst; check_condition(ls, v.v.k == VCALL, "syntax error"); - SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ + inst = &getinstruction(fs, &v.v); + SETARG_C(*inst, 1); /* call statement uses no results */ } } @@ -1502,26 +1814,25 @@ static void retstat (LexState *ls) { /* stat -> RETURN [explist] [';'] */ FuncState *fs = ls->fs; expdesc e; - int first, nret; /* registers with returned values */ + int nret; /* number of values being returned */ + int first = luaY_nvarstack(fs); /* first slot to be returned */ if (block_follow(ls, 1) || ls->t.token == ';') - first = nret = 0; /* return no values */ + nret = 0; /* return no values */ else { nret = explist(ls, &e); /* optional return values */ if (hasmultret(e.k)) { luaK_setmultret(fs, &e); - if (e.k == VCALL && nret == 1) { /* tail call? */ - SET_OPCODE(getcode(fs,&e), OP_TAILCALL); - lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); + if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */ + SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getinstruction(fs,&e)) == luaY_nvarstack(fs)); } - first = fs->nactvar; nret = LUA_MULTRET; /* return all values */ } else { if (nret == 1) /* only one single value? */ - first = luaK_exp2anyreg(fs, &e); - else { - luaK_exp2nextreg(fs, &e); /* values must go to the stack */ - first = fs->nactvar; /* return all active values */ + first = luaK_exp2anyreg(fs, &e); /* can use original slot */ + else { /* values must go to the top of the stack */ + luaK_exp2nextreg(fs, &e); lua_assert(nret == fs->freereg - first); } } @@ -1583,9 +1894,13 @@ static void statement (LexState *ls) { retstat(ls); break; } - case TK_BREAK: /* stat -> breakstat */ + case TK_BREAK: { /* stat -> breakstat */ + breakstat(ls); + break; + } case TK_GOTO: { /* stat -> 'goto' NAME */ - gotostat(ls, luaK_jump(ls->fs)); + luaX_next(ls); /* skip 'goto' */ + gotostat(ls); break; } default: { /* stat -> func | assignment */ @@ -1594,8 +1909,8 @@ static void statement (LexState *ls) { } } lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && - ls->fs->freereg >= ls->fs->nactvar); - ls->fs->freereg = ls->fs->nactvar; /* free registers */ + ls->fs->freereg >= luaY_nvarstack(ls->fs)); + ls->fs->freereg = luaY_nvarstack(ls->fs); /* free registers */ leavelevel(ls); } @@ -1608,11 +1923,15 @@ static void statement (LexState *ls) { */ static void mainfunc (LexState *ls, FuncState *fs) { BlockCnt bl; - expdesc v; + Upvaldesc *env; open_func(ls, fs, &bl); - fs->f->is_vararg = 1; /* main function is always vararg */ - init_exp(&v, VLOCAL, 0); /* create and... */ - newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */ + setvararg(fs, 0); /* main function is always declared vararg */ + env = allocupvalue(fs); /* ...set environment upvalue */ + env->instack = 1; + env->idx = 0; + env->kind = VDKREG; + env->name = ls->envn; + luaC_objbarrier(ls->L, fs->f, env->name); luaX_next(ls); /* read first token */ statlist(ls); /* parse main body */ check(ls, TK_EOS); @@ -1625,14 +1944,15 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, LexState lexstate; FuncState funcstate; LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */ - setclLvalue(L, L->top, cl); /* anchor it (to avoid being collected) */ - incr_top(L); + setclLvalue2s(L, L->top.p, cl); /* anchor it (to avoid being collected) */ + luaD_inctop(L); lexstate.h = luaH_new(L); /* create table for scanner */ - sethvalue(L, L->top, lexstate.h); /* anchor it */ - incr_top(L); + sethvalue2s(L, L->top.p, lexstate.h); /* anchor it */ + luaD_inctop(L); funcstate.f = cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); funcstate.f->source = luaS_new(L, name); /* create and anchor TString */ - lua_assert(iswhite(funcstate.f)); /* do not need barrier here */ + luaC_objbarrier(L, funcstate.f, funcstate.f->source); lexstate.buff = buff; lexstate.dyd = dyd; dyd->actvar.n = dyd->gt.n = dyd->label.n = 0; @@ -1641,7 +1961,7 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs); /* all scopes should be correctly finished */ lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0); - L->top--; /* remove scanner's table */ + L->top.p--; /* remove scanner's table */ return cl; /* closure is on the stack, too */ } diff --git a/libs/lua/lparser.h b/libs/lua/lparser.h index 62c50cac..5e4500f1 100644 --- a/libs/lua/lparser.h +++ b/libs/lua/lparser.h @@ -1,5 +1,5 @@ /* -** $Id: lparser.h,v 1.74 2014/10/25 11:50:46 roberto Exp $ +** $Id: lparser.h $ ** Lua Parser ** See Copyright Notice in lua.h */ @@ -13,60 +13,106 @@ /* -** Expression descriptor +** Expression and variable descriptor. +** Code generation for variables and expressions can be delayed to allow +** optimizations; An 'expdesc' structure describes a potentially-delayed +** variable/expression. It has a description of its "main" value plus a +** list of conditional jumps that can also produce its value (generated +** by short-circuit operators 'and'/'or'). */ +/* kinds of variables/expressions */ typedef enum { - VVOID, /* no value */ - VNIL, - VTRUE, - VFALSE, - VK, /* info = index of constant in 'k' */ - VKFLT, /* nval = numerical float value */ - VKINT, /* nval = numerical integer value */ - VNONRELOC, /* info = result register */ - VLOCAL, /* info = local register */ - VUPVAL, /* info = index of upvalue in 'upvalues' */ - VINDEXED, /* t = table register/upvalue; idx = index R/K */ - VJMP, /* info = instruction pc */ - VRELOCABLE, /* info = instruction pc */ - VCALL, /* info = instruction pc */ - VVARARG /* info = instruction pc */ + VVOID, /* when 'expdesc' describes the last expression of a list, + this kind means an empty list (so, no expression) */ + VNIL, /* constant nil */ + VTRUE, /* constant true */ + VFALSE, /* constant false */ + VK, /* constant in 'k'; info = index of constant in 'k' */ + VKFLT, /* floating constant; nval = numerical float value */ + VKINT, /* integer constant; ival = numerical integer value */ + VKSTR, /* string constant; strval = TString address; + (string is fixed by the lexer) */ + VNONRELOC, /* expression has its value in a fixed register; + info = result register */ + VLOCAL, /* local variable; var.ridx = register index; + var.vidx = relative index in 'actvar.arr' */ + VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VCONST, /* compile-time variable; + info = absolute index in 'actvar.arr' */ + VINDEXED, /* indexed variable; + ind.t = table register; + ind.idx = key's R index */ + VINDEXUP, /* indexed upvalue; + ind.t = table upvalue; + ind.idx = key's K index */ + VINDEXI, /* indexed variable with constant integer; + ind.t = table register; + ind.idx = key's value */ + VINDEXSTR, /* indexed variable with literal string; + ind.t = table register; + ind.idx = key's K index */ + VJMP, /* expression is a test/comparison; + info = pc of corresponding jump instruction */ + VRELOC, /* expression can put result in any register; + info = instruction pc */ + VCALL, /* expression is a function call; info = instruction pc */ + VVARARG /* vararg expression; info = instruction pc */ } expkind; -#define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXED) -#define vkisinreg(k) ((k) == VNONRELOC || (k) == VLOCAL) +#define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXSTR) +#define vkisindexed(k) (VINDEXED <= (k) && (k) <= VINDEXSTR) + typedef struct expdesc { expkind k; union { - struct { /* for indexed variables (VINDEXED) */ - short idx; /* index (R/K) */ + lua_Integer ival; /* for VKINT */ + lua_Number nval; /* for VKFLT */ + TString *strval; /* for VKSTR */ + int info; /* for generic use */ + struct { /* for indexed variables */ + short idx; /* index (R or "long" K) */ lu_byte t; /* table (register or upvalue) */ - lu_byte vt; /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ } ind; - int info; /* for generic use */ - lua_Number nval; /* for VKFLT */ - lua_Integer ival; /* for VKINT */ + struct { /* for local variables */ + lu_byte ridx; /* register holding the variable */ + unsigned short vidx; /* compiler index (in 'actvar.arr') */ + } var; } u; int t; /* patch list of 'exit when true' */ int f; /* patch list of 'exit when false' */ } expdesc; -/* description of active local variable */ -typedef struct Vardesc { - short idx; /* variable index in stack */ +/* kinds of variables */ +#define VDKREG 0 /* regular */ +#define RDKCONST 1 /* constant */ +#define RDKTOCLOSE 2 /* to-be-closed */ +#define RDKCTC 3 /* compile-time constant */ + +/* description of an active local variable */ +typedef union Vardesc { + struct { + TValuefields; /* constant value (if it is a compile-time constant) */ + lu_byte kind; + lu_byte ridx; /* register holding the variable */ + short pidx; /* index of the variable in the Proto's 'locvars' array */ + TString *name; /* variable name */ + } vd; + TValue k; /* constant value (if any) */ } Vardesc; + /* description of pending goto statements and label statements */ typedef struct Labeldesc { TString *name; /* label identifier */ int pc; /* position in code */ int line; /* line where it appeared */ - lu_byte nactvar; /* local level where it appears in current block */ + lu_byte nactvar; /* number of active variables in that position */ + lu_byte close; /* goto that escapes upvalues */ } Labeldesc; @@ -80,7 +126,7 @@ typedef struct Labellist { /* dynamic structures used by the parser */ typedef struct Dyndata { - struct { /* list of active local variables */ + struct { /* list of all active local variables */ Vardesc *arr; int n; int size; @@ -102,17 +148,22 @@ typedef struct FuncState { struct BlockCnt *bl; /* chain of current blocks */ int pc; /* next position to code (equivalent to 'ncode') */ int lasttarget; /* 'label' of last 'jump label' */ - int jpc; /* list of pending jumps to 'pc' */ + int previousline; /* last line that was saved in 'lineinfo' */ int nk; /* number of elements in 'k' */ int np; /* number of elements in 'p' */ + int nabslineinfo; /* number of elements in 'abslineinfo' */ int firstlocal; /* index of first local var (in Dyndata array) */ - short nlocvars; /* number of elements in 'f->locvars' */ + int firstlabel; /* index of first label (in 'dyd->label->arr') */ + short ndebugvars; /* number of elements in 'f->locvars' */ lu_byte nactvar; /* number of active local variables */ lu_byte nups; /* number of upvalues */ lu_byte freereg; /* first free register */ + lu_byte iwthabs; /* instructions issued since last absolute line info */ + lu_byte needclose; /* function needs to close upvalues when returning */ } FuncState; +LUAI_FUNC int luaY_nvarstack (FuncState *fs); LUAI_FUNC LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, Dyndata *dyd, const char *name, int firstchar); diff --git a/libs/lua/lprefix.h b/libs/lua/lprefix.h index 02daa837..484f2ad6 100644 --- a/libs/lua/lprefix.h +++ b/libs/lua/lprefix.h @@ -1,5 +1,5 @@ /* -** $Id: lprefix.h,v 1.2 2014/12/29 16:54:13 roberto Exp $ +** $Id: lprefix.h $ ** Definitions for Lua code that must come before any other header file ** See Copyright Notice in lua.h */ @@ -33,7 +33,7 @@ /* ** Windows stuff */ -#if defined(_WIN32) /* { */ +#if defined(_WIN32) /* { */ #if !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS /* avoid warnings about ISO C functions */ diff --git a/libs/lua/lstate.c b/libs/lua/lstate.c index 12e51d24..7fefacba 100644 --- a/libs/lua/lstate.c +++ b/libs/lua/lstate.c @@ -1,5 +1,5 @@ /* -** $Id: lstate.c,v 2.128 2015/03/04 13:31:21 roberto Exp $ +** $Id: lstate.c $ ** Global State ** See Copyright Notice in lua.h */ @@ -28,25 +28,6 @@ #include "ltm.h" -#if !defined(LUAI_GCPAUSE) -#define LUAI_GCPAUSE 200 /* 200% */ -#endif - -#if !defined(LUAI_GCMUL) -#define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ -#endif - - -/* -** a macro to help the creation of a unique random seed when a state is -** created; the seed is used to randomize hashes. -*/ -#if !defined(luai_makeseed) -#include -#define luai_makeseed() cast(unsigned int, time(NULL)) -#endif - - /* ** thread state + extra space @@ -71,42 +52,66 @@ typedef struct LG { /* -** Compute an initial seed as random as possible. Rely on Address Space -** Layout Randomization (if present) to increase randomness.. +** A macro to create a "random" seed when a state is created; +** the seed is used to randomize string hashes. +*/ +#if !defined(luai_makeseed) + +#include + +/* +** Compute an initial seed with some level of randomness. +** Rely on Address Space Layout Randomization (if present) and +** current time. */ #define addbuff(b,p,e) \ - { size_t t = cast(size_t, e); \ - memcpy(buff + p, &t, sizeof(t)); p += sizeof(t); } + { size_t t = cast_sizet(e); \ + memcpy(b + p, &t, sizeof(t)); p += sizeof(t); } -static unsigned int makeseed (lua_State *L) { - char buff[4 * sizeof(size_t)]; - unsigned int h = luai_makeseed(); +static unsigned int luai_makeseed (lua_State *L) { + char buff[3 * sizeof(size_t)]; + unsigned int h = cast_uint(time(NULL)); int p = 0; addbuff(buff, p, L); /* heap variable */ addbuff(buff, p, &h); /* local variable */ - addbuff(buff, p, luaO_nilobject); /* global variable */ addbuff(buff, p, &lua_newstate); /* public function */ lua_assert(p == sizeof(buff)); return luaS_hash(buff, p, h); } +#endif + /* ** set GCdebt to a new value keeping the value (totalbytes + GCdebt) -** invariant +** invariant (and avoiding underflows in 'totalbytes') */ void luaE_setdebt (global_State *g, l_mem debt) { - g->totalbytes -= (debt - g->GCdebt); + l_mem tb = gettotalbytes(g); + lua_assert(tb > 0); + if (debt < tb - MAX_LMEM) + debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */ + g->totalbytes = tb - debt; g->GCdebt = debt; } +LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) { + UNUSED(L); UNUSED(limit); + return LUAI_MAXCCALLS; /* warning?? */ +} + + CallInfo *luaE_extendCI (lua_State *L) { - CallInfo *ci = luaM_new(L, CallInfo); + CallInfo *ci; + lua_assert(L->ci->next == NULL); + ci = luaM_new(L, CallInfo); lua_assert(L->ci->next == NULL); L->ci->next = ci; ci->previous = L->ci; ci->next = NULL; + ci->u.l.trap = 0; + L->nci++; return ci; } @@ -114,59 +119,94 @@ CallInfo *luaE_extendCI (lua_State *L) { /* ** free all CallInfo structures not in use by a thread */ -void luaE_freeCI (lua_State *L) { +static void freeCI (lua_State *L) { CallInfo *ci = L->ci; CallInfo *next = ci->next; ci->next = NULL; while ((ci = next) != NULL) { next = ci->next; luaM_free(L, ci); + L->nci--; } } /* -** free half of the CallInfo structures not in use by a thread +** free half of the CallInfo structures not in use by a thread, +** keeping the first one. */ void luaE_shrinkCI (lua_State *L) { - CallInfo *ci = L->ci; - while (ci->next != NULL) { /* while there is 'next' */ - CallInfo *next2 = ci->next->next; /* next's next */ - if (next2 == NULL) break; - luaM_free(L, ci->next); /* remove next */ - ci->next = next2; /* remove 'next' from the list */ - next2->previous = ci; - ci = next2; + CallInfo *ci = L->ci->next; /* first free CallInfo */ + CallInfo *next; + if (ci == NULL) + return; /* no extra elements */ + while ((next = ci->next) != NULL) { /* two extra elements? */ + CallInfo *next2 = next->next; /* next's next */ + ci->next = next2; /* remove next from the list */ + L->nci--; + luaM_free(L, next); /* free next */ + if (next2 == NULL) + break; /* no more elements */ + else { + next2->previous = ci; + ci = next2; /* continue */ + } } } +/* +** Called when 'getCcalls(L)' larger or equal to LUAI_MAXCCALLS. +** If equal, raises an overflow error. If value is larger than +** LUAI_MAXCCALLS (which means it is handling an overflow) but +** not much larger, does not report an error (to allow overflow +** handling to work). +*/ +void luaE_checkcstack (lua_State *L) { + if (getCcalls(L) == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11)) + luaD_throw(L, LUA_ERRERR); /* error while handling stack error */ +} + + +LUAI_FUNC void luaE_incCstack (lua_State *L) { + L->nCcalls++; + if (l_unlikely(getCcalls(L) >= LUAI_MAXCCALLS)) + luaE_checkcstack(L); +} + + static void stack_init (lua_State *L1, lua_State *L) { int i; CallInfo *ci; /* initialize stack array */ - L1->stack = luaM_newvector(L, BASIC_STACK_SIZE, TValue); - L1->stacksize = BASIC_STACK_SIZE; - for (i = 0; i < BASIC_STACK_SIZE; i++) - setnilvalue(L1->stack + i); /* erase new stack */ - L1->top = L1->stack; - L1->stack_last = L1->stack + L1->stacksize - EXTRA_STACK; + L1->stack.p = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue); + L1->tbclist.p = L1->stack.p; + for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) + setnilvalue(s2v(L1->stack.p + i)); /* erase new stack */ + L1->top.p = L1->stack.p; + L1->stack_last.p = L1->stack.p + BASIC_STACK_SIZE; /* initialize first ci */ ci = &L1->base_ci; ci->next = ci->previous = NULL; - ci->callstatus = 0; - ci->func = L1->top; - setnilvalue(L1->top++); /* 'function' entry for this 'ci' */ - ci->top = L1->top + LUA_MINSTACK; + ci->callstatus = CIST_C; + ci->func.p = L1->top.p; + ci->u.c.k = NULL; + ci->nresults = 0; + setnilvalue(s2v(L1->top.p)); /* 'function' entry for this 'ci' */ + L1->top.p++; + ci->top.p = L1->top.p + LUA_MINSTACK; L1->ci = ci; } static void freestack (lua_State *L) { - if (L->stack == NULL) + if (L->stack.p == NULL) return; /* stack not completely built yet */ L->ci = &L->base_ci; /* free the entire 'ci' list */ - luaE_freeCI(L); - luaM_freearray(L, L->stack, L->stacksize); /* free stack array */ + freeCI(L); + lua_assert(L->nci == 0); + luaM_freearray(L, L->stack.p, stacksize(L) + EXTRA_STACK); /* free stack */ } @@ -174,23 +214,19 @@ static void freestack (lua_State *L) { ** Create registry table and its predefined values */ static void init_registry (lua_State *L, global_State *g) { - TValue temp; /* create registry */ Table *registry = luaH_new(L); sethvalue(L, &g->l_registry, registry); luaH_resize(L, registry, LUA_RIDX_LAST, 0); /* registry[LUA_RIDX_MAINTHREAD] = L */ - setthvalue(L, &temp, L); /* temp = L */ - luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &temp); - /* registry[LUA_RIDX_GLOBALS] = table of globals */ - sethvalue(L, &temp, luaH_new(L)); /* temp = new table (global table) */ - luaH_setint(L, registry, LUA_RIDX_GLOBALS, &temp); + setthvalue(L, ®istry->array[LUA_RIDX_MAINTHREAD - 1], L); + /* registry[LUA_RIDX_GLOBALS] = new table (table of globals) */ + sethvalue(L, ®istry->array[LUA_RIDX_GLOBALS - 1], luaH_new(L)); } /* ** open parts of the state that may cause memory-allocation errors. -** ('g->version' != NULL flags that the state was completely build) */ static void f_luaopen (lua_State *L, void *ud) { global_State *g = G(L); @@ -200,8 +236,8 @@ static void f_luaopen (lua_State *L, void *ud) { luaS_init(L); luaT_init(L); luaX_init(L); - g->gcrunning = 1; /* allow gc */ - g->version = lua_version(NULL); + g->gcstp = 0; /* allow gc */ + setnilvalue(&g->nilvalue); /* now state is complete */ luai_userstateopen(L); } @@ -212,32 +248,35 @@ static void f_luaopen (lua_State *L, void *ud) { */ static void preinit_thread (lua_State *L, global_State *g) { G(L) = g; - L->stack = NULL; + L->stack.p = NULL; L->ci = NULL; - L->stacksize = 0; + L->nci = 0; L->twups = L; /* thread has no upvalues */ - L->errorJmp = NULL; L->nCcalls = 0; + L->errorJmp = NULL; L->hook = NULL; L->hookmask = 0; L->basehookcount = 0; L->allowhook = 1; resethookcount(L); L->openupval = NULL; - L->nny = 1; L->status = LUA_OK; L->errfunc = 0; + L->oldpc = 0; } static void close_state (lua_State *L) { global_State *g = G(L); - luaF_close(L, L->stack); /* close all upvalues for this thread */ - luaC_freeallobjects(L); /* collect all objects */ - if (g->version) /* closing a fully built state? */ + if (!completestate(g)) /* closing a partially built state? */ + luaC_freeallobjects(L); /* just collect its objects */ + else { /* closing a fully built state */ + L->ci = &L->base_ci; /* unwind CallInfo list */ + luaD_closeprotected(L, 1, LUA_OK); /* close all upvalues */ + luaC_freeallobjects(L); /* collect all objects */ luai_userstateclose(L); + } luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); - luaZ_freebuffer(L, &g->buff); freestack(L); lua_assert(gettotalbytes(g) == sizeof(LG)); (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ @@ -246,18 +285,15 @@ static void close_state (lua_State *L) { LUA_API lua_State *lua_newthread (lua_State *L) { global_State *g = G(L); + GCObject *o; lua_State *L1; lua_lock(L); luaC_checkGC(L); /* create new thread */ - L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l; - L1->marked = luaC_white(g); - L1->tt = LUA_TTHREAD; - /* link it on list 'allgc' */ - L1->next = g->allgc; - g->allgc = obj2gco(L1); + o = luaC_newobjdt(L, LUA_TTHREAD, sizeof(LX), offsetof(LX, l)); + L1 = gco2th(o); /* anchor it on L stack */ - setthvalue(L, L->top, L1); + setthvalue2s(L, L->top.p, L1); api_incr_top(L); preinit_thread(L1, g); L1->hookmask = L->hookmask; @@ -276,7 +312,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) { void luaE_freethread (lua_State *L, lua_State *L1) { LX *l = fromstate(L1); - luaF_close(L1, L1->stack); /* close all upvalues for this thread */ + luaF_closeupval(L1, L1->stack.p); /* close all upvalues */ lua_assert(L1->openupval == NULL); luai_userstatefree(L, L1); freestack(L1); @@ -284,6 +320,43 @@ void luaE_freethread (lua_State *L, lua_State *L1) { } +int luaE_resetthread (lua_State *L, int status) { + CallInfo *ci = L->ci = &L->base_ci; /* unwind CallInfo list */ + setnilvalue(s2v(L->stack.p)); /* 'function' entry for basic 'ci' */ + ci->func.p = L->stack.p; + ci->callstatus = CIST_C; + if (status == LUA_YIELD) + status = LUA_OK; + L->status = LUA_OK; /* so it can run __close metamethods */ + status = luaD_closeprotected(L, 1, status); + if (status != LUA_OK) /* errors? */ + luaD_seterrorobj(L, status, L->stack.p + 1); + else + L->top.p = L->stack.p + 1; + ci->top.p = L->top.p + LUA_MINSTACK; + luaD_reallocstack(L, cast_int(ci->top.p - L->stack.p), 0); + return status; +} + + +LUA_API int lua_closethread (lua_State *L, lua_State *from) { + int status; + lua_lock(L); + L->nCcalls = (from) ? getCcalls(from) : 0; + status = luaE_resetthread(L, L->status); + lua_unlock(L); + return status; +} + + +/* +** Deprecated! Use 'lua_closethread' instead. +*/ +LUA_API int lua_resetthread (lua_State *L) { + return lua_closethread(L, NULL); +} + + LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; @@ -292,35 +365,44 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { if (l == NULL) return NULL; L = &l->l.l; g = &l->g; - L->next = NULL; - L->tt = LUA_TTHREAD; + L->tt = LUA_VTHREAD; g->currentwhite = bitmask(WHITE0BIT); L->marked = luaC_white(g); preinit_thread(L, g); + g->allgc = obj2gco(L); /* by now, only object is the main thread */ + L->next = NULL; + incnny(L); /* main thread is always non yieldable */ g->frealloc = f; g->ud = ud; + g->warnf = NULL; + g->ud_warn = NULL; g->mainthread = L; - g->seed = makeseed(L); - g->gcrunning = 0; /* no GC while building state */ - g->GCestimate = 0; + g->seed = luai_makeseed(L); + g->gcstp = GCSTPGC; /* no GC while building state */ g->strt.size = g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(&g->l_registry); - luaZ_initbuffer(L, &g->buff); g->panic = NULL; - g->version = NULL; g->gcstate = GCSpause; - g->gckind = KGC_NORMAL; - g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL; + g->gckind = KGC_INC; + g->gcstopem = 0; + g->gcemergency = 0; + g->finobj = g->tobefnz = g->fixedgc = NULL; + g->firstold1 = g->survival = g->old1 = g->reallyold = NULL; + g->finobjsur = g->finobjold1 = g->finobjrold = NULL; g->sweepgc = NULL; g->gray = g->grayagain = NULL; g->weak = g->ephemeron = g->allweak = NULL; g->twups = NULL; g->totalbytes = sizeof(LG); g->GCdebt = 0; - g->gcfinnum = 0; - g->gcpause = LUAI_GCPAUSE; - g->gcstepmul = LUAI_GCMUL; + g->lastatomic = 0; + setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */ + setgcparam(g->gcpause, LUAI_GCPAUSE); + setgcparam(g->gcstepmul, LUAI_GCMUL); + g->gcstepsize = LUAI_GCSTEPSIZE; + setgcparam(g->genmajormul, LUAI_GENMAJORMUL); + g->genminormul = LUAI_GENMINORMUL; for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { /* memory allocation error: free partial state */ @@ -332,9 +414,32 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { LUA_API void lua_close (lua_State *L) { - L = G(L)->mainthread; /* only the main thread can be closed */ lua_lock(L); + L = G(L)->mainthread; /* only the main thread can be closed */ close_state(L); } +void luaE_warning (lua_State *L, const char *msg, int tocont) { + lua_WarnFunction wf = G(L)->warnf; + if (wf != NULL) + wf(G(L)->ud_warn, msg, tocont); +} + + +/* +** Generate a warning from an error message +*/ +void luaE_warnerror (lua_State *L, const char *where) { + TValue *errobj = s2v(L->top.p - 1); /* error object */ + const char *msg = (ttisstring(errobj)) + ? getstr(tsvalue(errobj)) + : "error object is not a string"; + /* produce warning "error in %s (%s)" (where, msg) */ + luaE_warning(L, "error in ", 1); + luaE_warning(L, where, 1); + luaE_warning(L, " (", 1); + luaE_warning(L, msg, 1); + luaE_warning(L, ")", 0); +} + diff --git a/libs/lua/lstate.h b/libs/lua/lstate.h index eefc217d..007704c8 100644 --- a/libs/lua/lstate.h +++ b/libs/lua/lstate.h @@ -1,5 +1,5 @@ /* -** $Id: lstate.h,v 2.122 2015/06/01 16:34:37 roberto Exp $ +** $Id: lstate.h $ ** Global State ** See Copyright Notice in lua.h */ @@ -9,13 +9,17 @@ #include "lua.h" + +/* Some header files included here need this definition */ +typedef struct CallInfo CallInfo; + + #include "lobject.h" #include "ltm.h" #include "lzio.h" /* - ** Some notes about garbage-collected objects: All objects in Lua must ** be kept somehow accessible until being freed, so all objects always ** belong to one (and only one) of these lists, using field 'next' of @@ -23,27 +27,129 @@ ** ** 'allgc': all objects not marked for finalization; ** 'finobj': all objects marked for finalization; -** 'tobefnz': all objects ready to be finalized; +** 'tobefnz': all objects ready to be finalized; ** 'fixedgc': all objects that are not to be collected (currently ** only small strings, such as reserved words). +** +** For the generational collector, some of these lists have marks for +** generations. Each mark points to the first element in the list for +** that particular generation; that generation goes until the next mark. +** +** 'allgc' -> 'survival': new objects; +** 'survival' -> 'old': objects that survived one collection; +** 'old1' -> 'reallyold': objects that became old in last collection; +** 'reallyold' -> NULL: objects old for more than one cycle. +** +** 'finobj' -> 'finobjsur': new objects marked for finalization; +** 'finobjsur' -> 'finobjold1': survived """"; +** 'finobjold1' -> 'finobjrold': just old """"; +** 'finobjrold' -> NULL: really old """". +** +** All lists can contain elements older than their main ages, due +** to 'luaC_checkfinalizer' and 'udata2finalize', which move +** objects between the normal lists and the "marked for finalization" +** lists. Moreover, barriers can age young objects in young lists as +** OLD0, which then become OLD1. However, a list never contains +** elements younger than their main ages. +** +** The generational collector also uses a pointer 'firstold1', which +** points to the first OLD1 object in the list. It is used to optimize +** 'markold'. (Potentially OLD1 objects can be anywhere between 'allgc' +** and 'reallyold', but often the list has no OLD1 objects or they are +** after 'old1'.) Note the difference between it and 'old1': +** 'firstold1': no OLD1 objects before this point; there can be all +** ages after it. +** 'old1': no objects younger than OLD1 after this point. +*/ + +/* +** Moreover, there is another set of lists that control gray objects. +** These lists are linked by fields 'gclist'. (All objects that +** can become gray have such a field. The field is not the same +** in all objects, but it always has this name.) Any gray object +** must belong to one of these lists, and all objects in these lists +** must be gray (with two exceptions explained below): +** +** 'gray': regular gray objects, still waiting to be visited. +** 'grayagain': objects that must be revisited at the atomic phase. +** That includes +** - black objects got in a write barrier; +** - all kinds of weak tables during propagation phase; +** - all threads. +** 'weak': tables with weak values to be cleared; +** 'ephemeron': ephemeron tables with white->white entries; +** 'allweak': tables with weak keys and/or weak values to be cleared. +** +** The exceptions to that "gray rule" are: +** - TOUCHED2 objects in generational mode stay in a gray list (because +** they must be visited again at the end of the cycle), but they are +** marked black because assignments to them must activate barriers (to +** move them back to TOUCHED1). +** - Open upvales are kept gray to avoid barriers, but they stay out +** of gray lists. (They don't even have a 'gclist' field.) +*/ + + +/* +** About 'nCcalls': This count has two parts: the lower 16 bits counts +** the number of recursive invocations in the C stack; the higher +** 16 bits counts the number of non-yieldable calls in the stack. +** (They are together so that we can change and save both with one +** instruction.) */ +/* true if this thread does not have non-yieldable calls in the stack */ +#define yieldable(L) (((L)->nCcalls & 0xffff0000) == 0) + +/* real number of C calls */ +#define getCcalls(L) ((L)->nCcalls & 0xffff) + + +/* Increment the number of non-yieldable calls */ +#define incnny(L) ((L)->nCcalls += 0x10000) + +/* Decrement the number of non-yieldable calls */ +#define decnny(L) ((L)->nCcalls -= 0x10000) + +/* Non-yieldable call increment */ +#define nyci (0x10000 | 1) + + + + struct lua_longjmp; /* defined in ldo.c */ +/* +** Atomic type (relative to signals) to better ensure that 'lua_sethook' +** is thread safe +*/ +#if !defined(l_signalT) +#include +#define l_signalT sig_atomic_t +#endif -/* extra stack space to handle TM calls and some other extras */ + +/* +** Extra stack space to handle TM calls and some other extras. This +** space is not included in 'stack_last'. It is used only to avoid stack +** checks, either because the element will be promptly popped or because +** there will be a stack check soon after the push. Function frames +** never use this extra space, so it does not need to be kept clean. +*/ #define EXTRA_STACK 5 #define BASIC_STACK_SIZE (2*LUA_MINSTACK) +#define stacksize(th) cast_int((th)->stack_last.p - (th)->stack.p) + /* kinds of Garbage Collection */ -#define KGC_NORMAL 0 -#define KGC_EMERGENCY 1 /* gc was forced by an allocation failure */ +#define KGC_INC 0 /* incremental gc */ +#define KGC_GEN 1 /* generational gc */ typedef struct stringtable { @@ -55,21 +161,28 @@ typedef struct stringtable { /* ** Information about a call. -** When a thread yields, 'func' is adjusted to pretend that the -** top function has only the yielded values in its stack; in that -** case, the actual 'func' value is saved in field 'extra'. -** When a function calls another with a continuation, 'extra' keeps -** the function index so that, in case of errors, the continuation -** function can be called with the correct top. +** About union 'u': +** - field 'l' is used only for Lua functions; +** - field 'c' is used only for C functions. +** About union 'u2': +** - field 'funcidx' is used only by C functions while doing a +** protected call; +** - field 'nyield' is used only while a function is "doing" an +** yield (from the yield until the next resume); +** - field 'nres' is used only while closing tbc variables when +** returning from a function; +** - field 'transferinfo' is used only during call/returnhooks, +** before the function starts or after it ends. */ -typedef struct CallInfo { - StkId func; /* function index in the stack */ - StkId top; /* top for this function */ +struct CallInfo { + StkIdRel func; /* function index in the stack */ + StkIdRel top; /* top for this function */ struct CallInfo *previous, *next; /* dynamic call link */ union { struct { /* only for Lua functions */ - StkId base; /* base for this function */ const Instruction *savedpc; + volatile l_signalT trap; /* function is tracing lines/counts */ + int nextraargs; /* # of extra arguments in vararg functions */ } l; struct { /* only for C functions */ lua_KFunction k; /* continuation in case of yields */ @@ -77,26 +190,58 @@ typedef struct CallInfo { lua_KContext ctx; /* context info. in case of yields */ } c; } u; - ptrdiff_t extra; + union { + int funcidx; /* called-function index */ + int nyield; /* number of values yielded */ + int nres; /* number of values returned */ + struct { /* info about transferred values (for call/return hooks) */ + unsigned short ftransfer; /* offset of first value transferred */ + unsigned short ntransfer; /* number of values transferred */ + } transferinfo; + } u2; short nresults; /* expected number of results from this function */ - lu_byte callstatus; -} CallInfo; + unsigned short callstatus; +}; /* ** Bits in CallInfo status */ #define CIST_OAH (1<<0) /* original value of 'allowhook' */ -#define CIST_LUA (1<<1) /* call is running a Lua function */ -#define CIST_HOOKED (1<<2) /* call is running a debug hook */ -#define CIST_REENTRY (1<<3) /* call is running on same invocation of - luaV_execute of previous call */ -#define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ +#define CIST_C (1<<1) /* call is running a C function */ +#define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ +#define CIST_HOOKED (1<<3) /* call is running a debug hook */ +#define CIST_YPCALL (1<<4) /* doing a yieldable protected call */ #define CIST_TAIL (1<<5) /* call was tail called */ #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ -#define CIST_LEQ (1<<7) /* using __lt for __le */ +#define CIST_FIN (1<<7) /* function "called" a finalizer */ +#define CIST_TRAN (1<<8) /* 'ci' has transfer information */ +#define CIST_CLSRET (1<<9) /* function is closing tbc variables */ +/* Bits 10-12 are used for CIST_RECST (see below) */ +#define CIST_RECST 10 +#if defined(LUA_COMPAT_LT_LE) +#define CIST_LEQ (1<<13) /* using __lt for __le */ +#endif + + +/* +** Field CIST_RECST stores the "recover status", used to keep the error +** status while closing to-be-closed variables in coroutines, so that +** Lua can correctly resume after an yield from a __close method called +** because of an error. (Three bits are enough for error status.) +*/ +#define getcistrecst(ci) (((ci)->callstatus >> CIST_RECST) & 7) +#define setcistrecst(ci,st) \ + check_exp(((st) & 7) == (st), /* status must fit in three bits */ \ + ((ci)->callstatus = ((ci)->callstatus & ~(7 << CIST_RECST)) \ + | ((st) << CIST_RECST))) + + +/* active function is a Lua function */ +#define isLua(ci) (!((ci)->callstatus & CIST_C)) -#define isLua(ci) ((ci)->callstatus & CIST_LUA) +/* call is running Lua code (not a hook) */ +#define isLuacode(ci) (!((ci)->callstatus & (CIST_C | CIST_HOOKED))) /* assume that CIST_OAH has offset 0 and that 'v' is strictly 0/1 */ #define setoah(st,v) ((st) = ((st) & ~CIST_OAH) | (v)) @@ -109,17 +254,25 @@ typedef struct CallInfo { typedef struct global_State { lua_Alloc frealloc; /* function to reallocate memory */ void *ud; /* auxiliary data to 'frealloc' */ - lu_mem totalbytes; /* number of bytes currently allocated - GCdebt */ + l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ - lu_mem GCmemtrav; /* memory traversed by the GC */ lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ + lu_mem lastatomic; /* see function 'genstep' in file 'lgc.c' */ stringtable strt; /* hash table for strings */ TValue l_registry; + TValue nilvalue; /* a nil value */ unsigned int seed; /* randomized seed for hashes */ lu_byte currentwhite; lu_byte gcstate; /* state of garbage collector */ lu_byte gckind; /* kind of GC running */ - lu_byte gcrunning; /* true if GC is running */ + lu_byte gcstopem; /* stops emergency collections */ + lu_byte genminormul; /* control for minor generational collections */ + lu_byte genmajormul; /* control for major generational collections */ + lu_byte gcstp; /* control whether GC is running */ + lu_byte gcemergency; /* true if this is an emergency collection */ + lu_byte gcpause; /* size of pause between successive GCs */ + lu_byte gcstepmul; /* GC "speed" */ + lu_byte gcstepsize; /* (log2 of) GC granularity */ GCObject *allgc; /* list of all collectable objects */ GCObject **sweepgc; /* current position of sweep in list */ GCObject *finobj; /* list of collectable objects with finalizers */ @@ -130,18 +283,23 @@ typedef struct global_State { GCObject *allweak; /* list of all-weak tables */ GCObject *tobefnz; /* list of userdata to be GC */ GCObject *fixedgc; /* list of objects not to be collected */ + /* fields for generational collector */ + GCObject *survival; /* start of objects that survived one GC cycle */ + GCObject *old1; /* start of old1 objects */ + GCObject *reallyold; /* objects more than one cycle old ("really old") */ + GCObject *firstold1; /* first OLD1 object in the list (if any) */ + GCObject *finobjsur; /* list of survival objects with finalizers */ + GCObject *finobjold1; /* list of old1 objects with finalizers */ + GCObject *finobjrold; /* list of really old objects with finalizers */ struct lua_State *twups; /* list of threads with open upvalues */ - Mbuffer buff; /* temporary buffer for string concatenation */ - unsigned int gcfinnum; /* number of finalizers to call in each GC step */ - int gcpause; /* size of pause between successive GCs */ - int gcstepmul; /* GC 'granularity' */ lua_CFunction panic; /* to be called in unprotected errors */ struct lua_State *mainthread; - const lua_Number *version; /* pointer to version number */ - TString *memerrmsg; /* memory-error message */ + TString *memerrmsg; /* message for memory-allocation errors */ TString *tmname[TM_N]; /* array with tag-method names */ - struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */ - TString *strcache[STRCACHE_SIZE][1]; /* cache for strings in API */ + struct Table *mt[LUA_NUMTYPES]; /* metatables for basic types */ + TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ + lua_WarnFunction warnf; /* warning function */ + void *ud_warn; /* auxiliary data to 'warnf' */ } global_State; @@ -151,34 +309,46 @@ typedef struct global_State { struct lua_State { CommonHeader; lu_byte status; - StkId top; /* first free slot in the stack */ + lu_byte allowhook; + unsigned short nci; /* number of items in 'ci' list */ + StkIdRel top; /* first free slot in the stack */ global_State *l_G; CallInfo *ci; /* call info for current function */ - const Instruction *oldpc; /* last pc traced */ - StkId stack_last; /* last free slot in the stack */ - StkId stack; /* stack base */ + StkIdRel stack_last; /* end of stack (last element + 1) */ + StkIdRel stack; /* stack base */ UpVal *openupval; /* list of open upvalues in this stack */ + StkIdRel tbclist; /* list of to-be-closed variables */ GCObject *gclist; struct lua_State *twups; /* list of threads with open upvalues */ struct lua_longjmp *errorJmp; /* current error recover point */ CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ - lua_Hook hook; + volatile lua_Hook hook; ptrdiff_t errfunc; /* current error handling function (stack index) */ - int stacksize; + l_uint32 nCcalls; /* number of nested (non-yieldable | C) calls */ + int oldpc; /* last pc traced */ int basehookcount; int hookcount; - unsigned short nny; /* number of non-yieldable calls in stack */ - unsigned short nCcalls; /* number of nested C calls */ - lu_byte hookmask; - lu_byte allowhook; + volatile l_signalT hookmask; }; #define G(L) (L->l_G) +/* +** 'g->nilvalue' being a nil value flags that the state was completely +** build. +*/ +#define completestate(g) ttisnil(&g->nilvalue) + /* ** Union of all collectable objects (only for conversions) +** ISO C99, 6.5.2.3 p.5: +** "if a union contains several structures that share a common initial +** sequence [...], and if the union object currently contains one +** of these structures, it is permitted to inspect the common initial +** part of any of them anywhere that a declaration of the complete type +** of the union is visible." */ union GCUnion { GCObject gc; /* common header */ @@ -188,37 +358,50 @@ union GCUnion { struct Table h; struct Proto p; struct lua_State th; /* thread */ + struct UpVal upv; }; +/* +** ISO C99, 6.7.2.1 p.14: +** "A pointer to a union object, suitably converted, points to each of +** its members [...], and vice versa." +*/ #define cast_u(o) cast(union GCUnion *, (o)) /* macros to convert a GCObject into a specific value */ #define gco2ts(o) \ check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts)) -#define gco2u(o) check_exp((o)->tt == LUA_TUSERDATA, &((cast_u(o))->u)) -#define gco2lcl(o) check_exp((o)->tt == LUA_TLCL, &((cast_u(o))->cl.l)) -#define gco2ccl(o) check_exp((o)->tt == LUA_TCCL, &((cast_u(o))->cl.c)) +#define gco2u(o) check_exp((o)->tt == LUA_VUSERDATA, &((cast_u(o))->u)) +#define gco2lcl(o) check_exp((o)->tt == LUA_VLCL, &((cast_u(o))->cl.l)) +#define gco2ccl(o) check_exp((o)->tt == LUA_VCCL, &((cast_u(o))->cl.c)) #define gco2cl(o) \ check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl)) -#define gco2t(o) check_exp((o)->tt == LUA_TTABLE, &((cast_u(o))->h)) -#define gco2p(o) check_exp((o)->tt == LUA_TPROTO, &((cast_u(o))->p)) -#define gco2th(o) check_exp((o)->tt == LUA_TTHREAD, &((cast_u(o))->th)) +#define gco2t(o) check_exp((o)->tt == LUA_VTABLE, &((cast_u(o))->h)) +#define gco2p(o) check_exp((o)->tt == LUA_VPROTO, &((cast_u(o))->p)) +#define gco2th(o) check_exp((o)->tt == LUA_VTHREAD, &((cast_u(o))->th)) +#define gco2upv(o) check_exp((o)->tt == LUA_VUPVAL, &((cast_u(o))->upv)) -/* macro to convert a Lua object into a GCObject */ -#define obj2gco(v) \ - check_exp(novariant((v)->tt) < LUA_TDEADKEY, (&(cast_u(v)->gc))) +/* +** macro to convert a Lua object into a GCObject +** (The access to 'tt' tries to ensure that 'v' is actually a Lua object.) +*/ +#define obj2gco(v) check_exp((v)->tt >= LUA_TSTRING, &(cast_u(v)->gc)) /* actual number of total bytes allocated */ -#define gettotalbytes(g) ((g)->totalbytes + (g)->GCdebt) +#define gettotalbytes(g) cast(lu_mem, (g)->totalbytes + (g)->GCdebt) LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); -LUAI_FUNC void luaE_freeCI (lua_State *L); LUAI_FUNC void luaE_shrinkCI (lua_State *L); +LUAI_FUNC void luaE_checkcstack (lua_State *L); +LUAI_FUNC void luaE_incCstack (lua_State *L); +LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont); +LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where); +LUAI_FUNC int luaE_resetthread (lua_State *L, int status); #endif diff --git a/libs/lua/lstring.c b/libs/lua/lstring.c index 5e0e3c40..97757355 100644 --- a/libs/lua/lstring.c +++ b/libs/lua/lstring.c @@ -1,5 +1,5 @@ /* -** $Id: lstring.c,v 2.49 2015/06/01 16:34:37 roberto Exp $ +** $Id: lstring.c $ ** String table (keeps all strings handled by Lua) ** See Copyright Notice in lua.h */ @@ -22,16 +22,10 @@ #include "lstring.h" -#define MEMERRMSG "not enough memory" - - /* -** Lua will use at most ~(2^LUAI_HASHLIMIT) bytes from a string to -** compute its hash +** Maximum size for string table. */ -#if !defined(LUAI_HASHLIMIT) -#define LUAI_HASHLIMIT 5 -#endif +#define MAXSTRTB cast_int(luaM_limitN(MAX_INT, TString*)) /* @@ -39,51 +33,73 @@ */ int luaS_eqlngstr (TString *a, TString *b) { size_t len = a->u.lnglen; - lua_assert(a->tt == LUA_TLNGSTR && b->tt == LUA_TLNGSTR); + lua_assert(a->tt == LUA_VLNGSTR && b->tt == LUA_VLNGSTR); return (a == b) || /* same instance or... */ ((len == b->u.lnglen) && /* equal length and ... */ - (memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */ + (memcmp(getlngstr(a), getlngstr(b), len) == 0)); /* equal contents */ } unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) { - unsigned int h = seed ^ cast(unsigned int, l); - size_t l1; - size_t step = (l >> LUAI_HASHLIMIT) + 1; - for (l1 = l; l1 >= step; l1 -= step) - h = h ^ ((h<<5) + (h>>2) + cast_byte(str[l1 - 1])); + unsigned int h = seed ^ cast_uint(l); + for (; l > 0; l--) + h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1])); return h; } -/* -** resizes the string table -*/ -void luaS_resize (lua_State *L, int newsize) { - int i; - stringtable *tb = &G(L)->strt; - if (newsize > tb->size) { /* grow table if needed */ - luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *); - for (i = tb->size; i < newsize; i++) - tb->hash[i] = NULL; +unsigned int luaS_hashlongstr (TString *ts) { + lua_assert(ts->tt == LUA_VLNGSTR); + if (ts->extra == 0) { /* no hash? */ + size_t len = ts->u.lnglen; + ts->hash = luaS_hash(getlngstr(ts), len, ts->hash); + ts->extra = 1; /* now it has its hash */ } - for (i = 0; i < tb->size; i++) { /* rehash */ - TString *p = tb->hash[i]; - tb->hash[i] = NULL; - while (p) { /* for each node in the list */ + return ts->hash; +} + + +static void tablerehash (TString **vect, int osize, int nsize) { + int i; + for (i = osize; i < nsize; i++) /* clear new elements */ + vect[i] = NULL; + for (i = 0; i < osize; i++) { /* rehash old part of the array */ + TString *p = vect[i]; + vect[i] = NULL; + while (p) { /* for each string in the list */ TString *hnext = p->u.hnext; /* save next */ - unsigned int h = lmod(p->hash, newsize); /* new position */ - p->u.hnext = tb->hash[h]; /* chain it */ - tb->hash[h] = p; + unsigned int h = lmod(p->hash, nsize); /* new position */ + p->u.hnext = vect[h]; /* chain it into array */ + vect[h] = p; p = hnext; } } - if (newsize < tb->size) { /* shrink table if needed */ - /* vanishing slice should be empty */ - lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL); - luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *); +} + + +/* +** Resize the string table. If allocation fails, keep the current size. +** (This can degrade performance, but any non-zero size should work +** correctly.) +*/ +void luaS_resize (lua_State *L, int nsize) { + stringtable *tb = &G(L)->strt; + int osize = tb->size; + TString **newvect; + if (nsize < osize) /* shrinking table? */ + tablerehash(tb->hash, osize, nsize); /* depopulate shrinking part */ + newvect = luaM_reallocvector(L, tb->hash, osize, nsize, TString*); + if (l_unlikely(newvect == NULL)) { /* reallocation failed? */ + if (nsize < osize) /* was it shrinking table? */ + tablerehash(tb->hash, nsize, osize); /* restore to original size */ + /* leave table as it was */ + } + else { /* allocation succeeded */ + tb->hash = newvect; + tb->size = nsize; + if (nsize > osize) + tablerehash(newvect, osize, nsize); /* rehash for new size */ } - tb->size = newsize; } @@ -92,11 +108,12 @@ void luaS_resize (lua_State *L, int newsize) { ** a non-collectable string.) */ void luaS_clearcache (global_State *g) { - int i; - for (i = 0; i < STRCACHE_SIZE; i++) { - if (iswhite(g->strcache[i][0])) /* will entry be collected? */ - g->strcache[i][0] = g->memerrmsg; /* replace it with something fixed */ - } + int i, j; + for (i = 0; i < STRCACHE_N; i++) + for (j = 0; j < STRCACHE_M; j++) { + if (iswhite(g->strcache[i][j])) /* will entry be collected? */ + g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */ + } } @@ -105,13 +122,17 @@ void luaS_clearcache (global_State *g) { */ void luaS_init (lua_State *L) { global_State *g = G(L); - int i; - luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ + int i, j; + stringtable *tb = &G(L)->strt; + tb->hash = luaM_newvector(L, MINSTRTABSIZE, TString*); + tablerehash(tb->hash, 0, MINSTRTABSIZE); /* clear array */ + tb->size = MINSTRTABSIZE; /* pre-create memory-error message */ g->memerrmsg = luaS_newliteral(L, MEMERRMSG); luaC_fix(L, obj2gco(g->memerrmsg)); /* it should never be collected */ - for (i = 0; i < STRCACHE_SIZE; i++) /* fill cache with valid strings */ - g->strcache[i][0] = g->memerrmsg; + for (i = 0; i < STRCACHE_N; i++) /* fill cache with valid strings */ + for (j = 0; j < STRCACHE_M; j++) + g->strcache[i][j] = g->memerrmsg; } @@ -119,8 +140,7 @@ void luaS_init (lua_State *L) { /* ** creates a new string object */ -static TString *createstrobj (lua_State *L, const char *str, size_t l, - int tag, unsigned int h) { +static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { TString *ts; GCObject *o; size_t totalsize; /* total size of TString object */ @@ -129,8 +149,15 @@ static TString *createstrobj (lua_State *L, const char *str, size_t l, ts = gco2ts(o); ts->hash = h; ts->extra = 0; - memcpy(getaddrstr(ts), str, l * sizeof(char)); - getaddrstr(ts)[l] = '\0'; /* ending 0 */ + getstr(ts)[l] = '\0'; /* ending 0 */ + return ts; +} + + +TString *luaS_createlngstrobj (lua_State *L, size_t l) { + TString *ts = createstrobj(L, l, LUA_VLNGSTR, G(L)->seed); + ts->u.lnglen = l; + ts->shrlen = 0xFF; /* signals that it is a long string */ return ts; } @@ -145,32 +172,46 @@ void luaS_remove (lua_State *L, TString *ts) { } +static void growstrtab (lua_State *L, stringtable *tb) { + if (l_unlikely(tb->nuse == MAX_INT)) { /* too many strings? */ + luaC_fullgc(L, 1); /* try to free some... */ + if (tb->nuse == MAX_INT) /* still too many? */ + luaM_error(L); /* cannot even create a message... */ + } + if (tb->size <= MAXSTRTB / 2) /* can grow string table? */ + luaS_resize(L, tb->size * 2); +} + + /* -** checks whether short string exists and reuses it or creates a new one +** Checks whether short string exists and reuses it or creates a new one. */ static TString *internshrstr (lua_State *L, const char *str, size_t l) { TString *ts; global_State *g = G(L); + stringtable *tb = &g->strt; unsigned int h = luaS_hash(str, l, g->seed); - TString **list = &g->strt.hash[lmod(h, g->strt.size)]; + TString **list = &tb->hash[lmod(h, tb->size)]; + lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ for (ts = *list; ts != NULL; ts = ts->u.hnext) { - if (l == ts->shrlen && - (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { + if (l == ts->shrlen && (memcmp(str, getshrstr(ts), l * sizeof(char)) == 0)) { /* found! */ if (isdead(g, ts)) /* dead (but not collected yet)? */ changewhite(ts); /* resurrect it */ return ts; } } - if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) { - luaS_resize(L, g->strt.size * 2); - list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */ + /* else must create a new string */ + if (tb->nuse >= tb->size) { /* need to grow string table? */ + growstrtab(L, tb); + list = &tb->hash[lmod(h, tb->size)]; /* rehash with new size */ } - ts = createstrobj(L, str, l, LUA_TSHRSTR, h); + ts = createstrobj(L, l, LUA_VSHRSTR, h); ts->shrlen = cast_byte(l); + memcpy(getshrstr(ts), str, l * sizeof(char)); ts->u.hnext = *list; *list = ts; - g->strt.nuse++; + tb->nuse++; return ts; } @@ -183,10 +224,10 @@ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { return internshrstr(L, str, l); else { TString *ts; - if (l + 1 > (MAX_SIZE - sizeof(TString))/sizeof(char)) + if (l_unlikely(l * sizeof(char) >= (MAX_SIZE - sizeof(TString)))) luaM_toobig(L); - ts = createstrobj(L, str, l, LUA_TLNGSTR, G(L)->seed); - ts->u.lnglen = l; + ts = luaS_createlngstrobj(L, l); + memcpy(getlngstr(ts), str, l * sizeof(char)); return ts; } } @@ -199,28 +240,35 @@ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { ** check hits. */ TString *luaS_new (lua_State *L, const char *str) { - unsigned int i = point2uint(str) % STRCACHE_SIZE; /* hash */ + unsigned int i = point2uint(str) % STRCACHE_N; /* hash */ + int j; TString **p = G(L)->strcache[i]; - if (strcmp(str, getstr(p[0])) == 0) /* hit? */ - return p[0]; /* that it is */ - else { /* normal route */ - TString *s = luaS_newlstr(L, str, strlen(str)); - p[0] = s; - return s; + for (j = 0; j < STRCACHE_M; j++) { + if (strcmp(str, getstr(p[j])) == 0) /* hit? */ + return p[j]; /* that is it */ } + /* normal route */ + for (j = STRCACHE_M - 1; j > 0; j--) + p[j] = p[j - 1]; /* move out last element */ + /* new element is first in the list */ + p[0] = luaS_newlstr(L, str, strlen(str)); + return p[0]; } -Udata *luaS_newudata (lua_State *L, size_t s) { +Udata *luaS_newudata (lua_State *L, size_t s, int nuvalue) { Udata *u; + int i; GCObject *o; - if (s > MAX_SIZE - sizeof(Udata)) + if (l_unlikely(s > MAX_SIZE - udatamemoffset(nuvalue))) luaM_toobig(L); - o = luaC_newobj(L, LUA_TUSERDATA, sizeludata(s)); + o = luaC_newobj(L, LUA_VUSERDATA, sizeudata(nuvalue, s)); u = gco2u(o); u->len = s; + u->nuvalue = nuvalue; u->metatable = NULL; - setuservalue(L, u, luaO_nilobject); + for (i = 0; i < nuvalue; i++) + setnilvalue(&u->uv[i].uv); return u; } diff --git a/libs/lua/lstring.h b/libs/lua/lstring.h index e746f5fc..450c2390 100644 --- a/libs/lua/lstring.h +++ b/libs/lua/lstring.h @@ -1,5 +1,5 @@ /* -** $Id: lstring.h,v 1.59 2015/03/25 13:42:19 roberto Exp $ +** $Id: lstring.h $ ** String table (keep all strings handled by Lua) ** See Copyright Notice in lua.h */ @@ -12,10 +12,18 @@ #include "lstate.h" -#define sizelstring(l) (sizeof(union UTString) + ((l) + 1) * sizeof(char)) +/* +** Memory-allocation error message must be preallocated (it cannot +** be created after memory is exhausted) +*/ +#define MEMERRMSG "not enough memory" + -#define sizeludata(l) (sizeof(union UUdata) + (l)) -#define sizeudata(u) sizeludata((u)->len) +/* +** Size of a TString: Size of the header plus space for the string +** itself (including final '\0'). +*/ +#define sizelstring(l) (offsetof(TString, contents) + ((l) + 1) * sizeof(char)) #define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ (sizeof(s)/sizeof(char))-1)) @@ -24,24 +32,26 @@ /* ** test whether a string is a reserved word */ -#define isreserved(s) ((s)->tt == LUA_TSHRSTR && (s)->extra > 0) +#define isreserved(s) ((s)->tt == LUA_VSHRSTR && (s)->extra > 0) /* ** equality for short strings, which are always internalized */ -#define eqshrstr(a,b) check_exp((a)->tt == LUA_TSHRSTR, (a) == (b)) +#define eqshrstr(a,b) check_exp((a)->tt == LUA_VSHRSTR, (a) == (b)) LUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed); +LUAI_FUNC unsigned int luaS_hashlongstr (TString *ts); LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); LUAI_FUNC void luaS_resize (lua_State *L, int newsize); LUAI_FUNC void luaS_clearcache (global_State *g); LUAI_FUNC void luaS_init (lua_State *L); LUAI_FUNC void luaS_remove (lua_State *L, TString *ts); -LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, int nuvalue); LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); LUAI_FUNC TString *luaS_new (lua_State *L, const char *str); +LUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l); #endif diff --git a/libs/lua/lstrlib.c b/libs/lua/lstrlib.c index 19c350de..03167161 100644 --- a/libs/lua/lstrlib.c +++ b/libs/lua/lstrlib.c @@ -1,5 +1,5 @@ /* -** $Id: lstrlib.c,v 1.229 2015/05/20 17:39:23 roberto Exp $ +** $Id: lstrlib.c $ ** Standard library for string operations and pattern-matching ** See Copyright Notice in lua.h */ @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -26,7 +28,8 @@ /* ** maximum number of captures that a pattern can do during -** pattern-matching. This limit is arbitrary. +** pattern-matching. This limit is arbitrary, but must fit in +** an unsigned char. */ #if !defined(LUA_MAXCAPTURES) #define LUA_MAXCAPTURES 32 @@ -41,8 +44,10 @@ ** Some sizes are better limited to fit in 'int', but must also fit in ** 'size_t'. (We assume that 'lua_Integer' cannot be smaller than 'int'.) */ +#define MAX_SIZET ((size_t)(~(size_t)0)) + #define MAXSIZE \ - (sizeof(size_t) < sizeof(int) ? (~(size_t)0) : (size_t)(INT_MAX)) + (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) @@ -55,23 +60,50 @@ static int str_len (lua_State *L) { } -/* translate a relative string position: negative means back from end */ -static lua_Integer posrelat (lua_Integer pos, size_t len) { - if (pos >= 0) return pos; - else if (0u - (size_t)pos > len) return 0; - else return (lua_Integer)len + pos + 1; +/* +** translate a relative initial string position +** (negative means back from end): clip result to [1, inf). +** The length of any string in Lua must fit in a lua_Integer, +** so there are no overflows in the casts. +** The inverted comparison avoids a possible overflow +** computing '-pos'. +*/ +static size_t posrelatI (lua_Integer pos, size_t len) { + if (pos > 0) + return (size_t)pos; + else if (pos == 0) + return 1; + else if (pos < -(lua_Integer)len) /* inverted comparison */ + return 1; /* clip to 1 */ + else return len + (size_t)pos + 1; +} + + +/* +** Gets an optional ending string position from argument 'arg', +** with default value 'def'. +** Negative means back from end: clip result to [0, len] +*/ +static size_t getendpos (lua_State *L, int arg, lua_Integer def, + size_t len) { + lua_Integer pos = luaL_optinteger(L, arg, def); + if (pos > (lua_Integer)len) + return len; + else if (pos >= 0) + return (size_t)pos; + else if (pos < -(lua_Integer)len) + return 0; + else return len + (size_t)pos + 1; } static int str_sub (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); - lua_Integer start = posrelat(luaL_checkinteger(L, 2), l); - lua_Integer end = posrelat(luaL_optinteger(L, 3, -1), l); - if (start < 1) start = 1; - if (end > (lua_Integer)l) end = l; + size_t start = posrelatI(luaL_checkinteger(L, 2), l); + size_t end = getendpos(L, 3, -1, l); if (start <= end) - lua_pushlstring(L, s + start - 1, (size_t)(end - start) + 1); + lua_pushlstring(L, s + start - 1, (end - start) + 1); else lua_pushliteral(L, ""); return 1; } @@ -120,8 +152,9 @@ static int str_rep (lua_State *L) { const char *s = luaL_checklstring(L, 1, &l); lua_Integer n = luaL_checkinteger(L, 2); const char *sep = luaL_optlstring(L, 3, "", &lsep); - if (n <= 0) lua_pushliteral(L, ""); - else if (l + lsep < l || l + lsep > MAXSIZE / n) /* may overflow? */ + if (n <= 0) + lua_pushliteral(L, ""); + else if (l_unlikely(l + lsep < l || l + lsep > MAXSIZE / n)) return luaL_error(L, "resulting string too large"); else { size_t totallen = (size_t)n * l + (size_t)(n - 1) * lsep; @@ -144,13 +177,12 @@ static int str_rep (lua_State *L) { static int str_byte (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); - lua_Integer posi = posrelat(luaL_optinteger(L, 2, 1), l); - lua_Integer pose = posrelat(luaL_optinteger(L, 3, posi), l); + lua_Integer pi = luaL_optinteger(L, 2, 1); + size_t posi = posrelatI(pi, l); + size_t pose = getendpos(L, 3, pi, l); int n, i; - if (posi < 1) posi = 1; - if (pose > (lua_Integer)l) pose = l; if (posi > pose) return 0; /* empty interval; return no values */ - if (pose - posi >= INT_MAX) /* arithmetic overflow? */ + if (l_unlikely(pose - posi >= (size_t)INT_MAX)) /* arithmetic overflow? */ return luaL_error(L, "string slice too long"); n = (int)(pose - posi) + 1; luaL_checkstack(L, n, "string slice too long"); @@ -166,8 +198,8 @@ static int str_char (lua_State *L) { luaL_Buffer b; char *p = luaL_buffinitsize(L, &b, n); for (i=1; i<=n; i++) { - lua_Integer c = luaL_checkinteger(L, i); - luaL_argcheck(L, uchar(c) == c, i, "value out of range"); + lua_Unsigned c = (lua_Unsigned)luaL_checkinteger(L, i); + luaL_argcheck(L, c <= (lua_Unsigned)UCHAR_MAX, i, "value out of range"); p[i - 1] = uchar(c); } luaL_pushresultsize(&b, n); @@ -175,27 +207,143 @@ static int str_char (lua_State *L) { } -static int writer (lua_State *L, const void *b, size_t size, void *B) { - (void)L; - luaL_addlstring((luaL_Buffer *) B, (const char *)b, size); +/* +** Buffer to store the result of 'string.dump'. It must be initialized +** after the call to 'lua_dump', to ensure that the function is on the +** top of the stack when 'lua_dump' is called. ('luaL_buffinit' might +** push stuff.) +*/ +struct str_Writer { + int init; /* true iff buffer has been initialized */ + luaL_Buffer B; +}; + + +static int writer (lua_State *L, const void *b, size_t size, void *ud) { + struct str_Writer *state = (struct str_Writer *)ud; + if (!state->init) { + state->init = 1; + luaL_buffinit(L, &state->B); + } + luaL_addlstring(&state->B, (const char *)b, size); return 0; } static int str_dump (lua_State *L) { - luaL_Buffer b; + struct str_Writer state; int strip = lua_toboolean(L, 2); luaL_checktype(L, 1, LUA_TFUNCTION); - lua_settop(L, 1); - luaL_buffinit(L,&b); - if (lua_dump(L, writer, &b, strip) != 0) + lua_settop(L, 1); /* ensure function is on the top of the stack */ + state.init = 0; + if (l_unlikely(lua_dump(L, writer, &state, strip) != 0)) return luaL_error(L, "unable to dump given function"); - luaL_pushresult(&b); + luaL_pushresult(&state.B); return 1; } +/* +** {====================================================== +** METAMETHODS +** ======================================================= +*/ + +#if defined(LUA_NOCVTS2N) /* { */ + +/* no coercion from strings to numbers */ + +static const luaL_Reg stringmetamethods[] = { + {"__index", NULL}, /* placeholder */ + {NULL, NULL} +}; + +#else /* }{ */ + +static int tonum (lua_State *L, int arg) { + if (lua_type(L, arg) == LUA_TNUMBER) { /* already a number? */ + lua_pushvalue(L, arg); + return 1; + } + else { /* check whether it is a numerical string */ + size_t len; + const char *s = lua_tolstring(L, arg, &len); + return (s != NULL && lua_stringtonumber(L, s) == len + 1); + } +} + + +static void trymt (lua_State *L, const char *mtname) { + lua_settop(L, 2); /* back to the original arguments */ + if (l_unlikely(lua_type(L, 2) == LUA_TSTRING || + !luaL_getmetafield(L, 2, mtname))) + luaL_error(L, "attempt to %s a '%s' with a '%s'", mtname + 2, + luaL_typename(L, -2), luaL_typename(L, -1)); + lua_insert(L, -3); /* put metamethod before arguments */ + lua_call(L, 2, 1); /* call metamethod */ +} + + +static int arith (lua_State *L, int op, const char *mtname) { + if (tonum(L, 1) && tonum(L, 2)) + lua_arith(L, op); /* result will be on the top */ + else + trymt(L, mtname); + return 1; +} + + +static int arith_add (lua_State *L) { + return arith(L, LUA_OPADD, "__add"); +} + +static int arith_sub (lua_State *L) { + return arith(L, LUA_OPSUB, "__sub"); +} + +static int arith_mul (lua_State *L) { + return arith(L, LUA_OPMUL, "__mul"); +} + +static int arith_mod (lua_State *L) { + return arith(L, LUA_OPMOD, "__mod"); +} + +static int arith_pow (lua_State *L) { + return arith(L, LUA_OPPOW, "__pow"); +} + +static int arith_div (lua_State *L) { + return arith(L, LUA_OPDIV, "__div"); +} + +static int arith_idiv (lua_State *L) { + return arith(L, LUA_OPIDIV, "__idiv"); +} + +static int arith_unm (lua_State *L) { + return arith(L, LUA_OPUNM, "__unm"); +} + + +static const luaL_Reg stringmetamethods[] = { + {"__add", arith_add}, + {"__sub", arith_sub}, + {"__mul", arith_mul}, + {"__mod", arith_mod}, + {"__pow", arith_pow}, + {"__div", arith_div}, + {"__idiv", arith_idiv}, + {"__unm", arith_unm}, + {"__index", NULL}, /* placeholder */ + {NULL, NULL} +}; + +#endif /* } */ + +/* }====================================================== */ + /* ** {====================================================== ** PATTERN MATCHING @@ -208,12 +356,12 @@ static int str_dump (lua_State *L) { typedef struct MatchState { - int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ const char *src_init; /* init of source string */ const char *src_end; /* end ('\0') of source string */ const char *p_end; /* end ('\0') of pattern */ lua_State *L; - int level; /* total number of captures (finished or unfinished) */ + int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ + unsigned char level; /* total number of captures (finished or unfinished) */ struct { const char *init; ptrdiff_t len; @@ -237,7 +385,8 @@ static const char *match (MatchState *ms, const char *s, const char *p); static int check_capture (MatchState *ms, int l) { l -= '1'; - if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) + if (l_unlikely(l < 0 || l >= ms->level || + ms->capture[l].len == CAP_UNFINISHED)) return luaL_error(ms->L, "invalid capture index %%%d", l + 1); return l; } @@ -254,14 +403,14 @@ static int capture_to_close (MatchState *ms) { static const char *classend (MatchState *ms, const char *p) { switch (*p++) { case L_ESC: { - if (p == ms->p_end) + if (l_unlikely(p == ms->p_end)) luaL_error(ms->L, "malformed pattern (ends with '%%')"); return p+1; } case '[': { if (*p == '^') p++; do { /* look for a ']' */ - if (p == ms->p_end) + if (l_unlikely(p == ms->p_end)) luaL_error(ms->L, "malformed pattern (missing ']')"); if (*(p++) == L_ESC && p < ms->p_end) p++; /* skip escapes (e.g. '%]') */ @@ -336,7 +485,7 @@ static int singlematch (MatchState *ms, const char *s, const char *p, static const char *matchbalance (MatchState *ms, const char *s, const char *p) { - if (p >= ms->p_end - 1) + if (l_unlikely(p >= ms->p_end - 1)) luaL_error(ms->L, "malformed pattern (missing arguments to '%%b')"); if (*s != *p) return NULL; else { @@ -419,9 +568,9 @@ static const char *match_capture (MatchState *ms, const char *s, int l) { static const char *match (MatchState *ms, const char *s, const char *p) { - if (ms->matchdepth-- == 0) + if (l_unlikely(ms->matchdepth-- == 0)) luaL_error(ms->L, "pattern too complex"); - init: /* using goto's to optimize tail recursion */ + init: /* using goto to optimize tail recursion */ if (p != ms->p_end) { /* end of pattern? */ switch (*p) { case '(': { /* start capture */ @@ -453,7 +602,7 @@ static const char *match (MatchState *ms, const char *s, const char *p) { case 'f': { /* frontier? */ const char *ep; char previous; p += 2; - if (*p != '[') + if (l_unlikely(*p != '[')) luaL_error(ms->L, "missing '[' after '%%f' in pattern"); ep = classend(ms, p); /* points to what is next */ previous = (s == ms->src_init) ? '\0' : *(s - 1); @@ -543,25 +692,46 @@ static const char *lmemfind (const char *s1, size_t l1, } -static void push_onecapture (MatchState *ms, int i, const char *s, - const char *e) { +/* +** get information about the i-th capture. If there are no captures +** and 'i==0', return information about the whole match, which +** is the range 's'..'e'. If the capture is a string, return +** its length and put its address in '*cap'. If it is an integer +** (a position), push it on the stack and return CAP_POSITION. +*/ +static size_t get_onecapture (MatchState *ms, int i, const char *s, + const char *e, const char **cap) { if (i >= ms->level) { - if (i == 0) /* ms->level == 0, too */ - lua_pushlstring(ms->L, s, e - s); /* add whole match */ - else + if (l_unlikely(i != 0)) luaL_error(ms->L, "invalid capture index %%%d", i + 1); + *cap = s; + return e - s; } else { - ptrdiff_t l = ms->capture[i].len; - if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); - if (l == CAP_POSITION) + ptrdiff_t capl = ms->capture[i].len; + *cap = ms->capture[i].init; + if (l_unlikely(capl == CAP_UNFINISHED)) + luaL_error(ms->L, "unfinished capture"); + else if (capl == CAP_POSITION) lua_pushinteger(ms->L, (ms->capture[i].init - ms->src_init) + 1); - else - lua_pushlstring(ms->L, ms->capture[i].init, l); + return capl; } } +/* +** Push the i-th capture on the stack. +*/ +static void push_onecapture (MatchState *ms, int i, const char *s, + const char *e) { + const char *cap; + ptrdiff_t l = get_onecapture(ms, i, s, e, &cap); + if (l != CAP_POSITION) + lua_pushlstring(ms->L, cap, l); + /* else position was already pushed */ +} + + static int push_captures (MatchState *ms, const char *s, const char *e) { int i; int nlevels = (ms->level == 0 && s) ? 1 : ms->level; @@ -584,20 +754,35 @@ static int nospecials (const char *p, size_t l) { } +static void prepstate (MatchState *ms, lua_State *L, + const char *s, size_t ls, const char *p, size_t lp) { + ms->L = L; + ms->matchdepth = MAXCCALLS; + ms->src_init = s; + ms->src_end = s + ls; + ms->p_end = p + lp; +} + + +static void reprepstate (MatchState *ms) { + ms->level = 0; + lua_assert(ms->matchdepth == MAXCCALLS); +} + + static int str_find_aux (lua_State *L, int find) { size_t ls, lp; const char *s = luaL_checklstring(L, 1, &ls); const char *p = luaL_checklstring(L, 2, &lp); - lua_Integer init = posrelat(luaL_optinteger(L, 3, 1), ls); - if (init < 1) init = 1; - else if (init > (lua_Integer)ls + 1) { /* start after string's end? */ - lua_pushnil(L); /* cannot find anything */ + size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1; + if (init > ls) { /* start after string's end? */ + luaL_pushfail(L); /* cannot find anything */ return 1; } /* explicit request or no special characters? */ if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) { /* do a plain search */ - const char *s2 = lmemfind(s + init - 1, ls - (size_t)init + 1, p, lp); + const char *s2 = lmemfind(s + init, ls - init, p, lp); if (s2) { lua_pushinteger(L, (s2 - s) + 1); lua_pushinteger(L, (s2 - s) + lp); @@ -606,20 +791,15 @@ static int str_find_aux (lua_State *L, int find) { } else { MatchState ms; - const char *s1 = s + init - 1; + const char *s1 = s + init; int anchor = (*p == '^'); if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s + ls; - ms.p_end = p + lp; + prepstate(&ms, L, s, ls, p, lp); do { const char *res; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); + reprepstate(&ms); if ((res=match(&ms, s1, p)) != NULL) { if (find) { lua_pushinteger(L, (s1 - s) + 1); /* start */ @@ -631,7 +811,7 @@ static int str_find_aux (lua_State *L, int find) { } } while (s1++ < ms.src_end && !anchor); } - lua_pushnil(L); /* not found */ + luaL_pushfail(L); /* not found */ return 1; } @@ -646,29 +826,25 @@ static int str_match (lua_State *L) { } +/* state for 'gmatch' */ +typedef struct GMatchState { + const char *src; /* current position */ + const char *p; /* pattern */ + const char *lastmatch; /* end of last match */ + MatchState ms; /* match state */ +} GMatchState; + + static int gmatch_aux (lua_State *L) { - MatchState ms; - size_t ls, lp; - const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); - const char *p = lua_tolstring(L, lua_upvalueindex(2), &lp); + GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3)); const char *src; - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = s; - ms.src_end = s+ls; - ms.p_end = p + lp; - for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); - src <= ms.src_end; - src++) { + gm->ms.L = L; + for (src = gm->src; src <= gm->ms.src_end; src++) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - if ((e = match(&ms, src, p)) != NULL) { - lua_Integer newstart = e-s; - if (e == src) newstart++; /* empty match? go at least one position */ - lua_pushinteger(L, newstart); - lua_replace(L, lua_upvalueindex(3)); - return push_captures(&ms, src, e); + reprepstate(&gm->ms); + if ((e = match(&gm->ms, src, gm->p)) != NULL && e != gm->lastmatch) { + gm->src = gm->lastmatch = e; + return push_captures(&gm->ms, src, e); } } return 0; /* not found */ @@ -676,10 +852,17 @@ static int gmatch_aux (lua_State *L) { static int gmatch (lua_State *L) { - luaL_checkstring(L, 1); - luaL_checkstring(L, 2); - lua_settop(L, 2); - lua_pushinteger(L, 0); + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1; + GMatchState *gm; + lua_settop(L, 2); /* keep strings on closure to avoid being collected */ + gm = (GMatchState *)lua_newuserdatauv(L, sizeof(GMatchState), 0); + if (init > ls) /* start after string's end? */ + init = ls + 1; /* avoid overflows in 's + init' */ + prepstate(&gm->ms, L, s, ls, p, lp); + gm->src = s + init; gm->p = p; gm->lastmatch = NULL; lua_pushcclosure(L, gmatch_aux, 3); return 1; } @@ -687,103 +870,114 @@ static int gmatch (lua_State *L) { static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { - size_t l, i; + size_t l; lua_State *L = ms->L; const char *news = lua_tolstring(L, 3, &l); - for (i = 0; i < l; i++) { - if (news[i] != L_ESC) - luaL_addchar(b, news[i]); - else { - i++; /* skip ESC */ - if (!isdigit(uchar(news[i]))) { - if (news[i] != L_ESC) - luaL_error(L, "invalid use of '%c' in replacement string", L_ESC); - luaL_addchar(b, news[i]); - } - else if (news[i] == '0') - luaL_addlstring(b, s, e - s); - else { - push_onecapture(ms, news[i] - '1', s, e); - luaL_tolstring(L, -1, NULL); /* if number, convert it to string */ - lua_remove(L, -2); /* remove original value */ - luaL_addvalue(b); /* add capture to accumulated result */ - } + const char *p; + while ((p = (char *)memchr(news, L_ESC, l)) != NULL) { + luaL_addlstring(b, news, p - news); + p++; /* skip ESC */ + if (*p == L_ESC) /* '%%' */ + luaL_addchar(b, *p); + else if (*p == '0') /* '%0' */ + luaL_addlstring(b, s, e - s); + else if (isdigit(uchar(*p))) { /* '%n' */ + const char *cap; + ptrdiff_t resl = get_onecapture(ms, *p - '1', s, e, &cap); + if (resl == CAP_POSITION) + luaL_addvalue(b); /* add position to accumulated result */ + else + luaL_addlstring(b, cap, resl); } + else + luaL_error(L, "invalid use of '%c' in replacement string", L_ESC); + l -= p + 1 - news; + news = p + 1; } + luaL_addlstring(b, news, l); } -static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, - const char *e, int tr) { +/* +** Add the replacement value to the string buffer 'b'. +** Return true if the original string was changed. (Function calls and +** table indexing resulting in nil or false do not change the subject.) +*/ +static int add_value (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e, int tr) { lua_State *L = ms->L; switch (tr) { - case LUA_TFUNCTION: { + case LUA_TFUNCTION: { /* call the function */ int n; - lua_pushvalue(L, 3); - n = push_captures(ms, s, e); - lua_call(L, n, 1); + lua_pushvalue(L, 3); /* push the function */ + n = push_captures(ms, s, e); /* all captures as arguments */ + lua_call(L, n, 1); /* call it */ break; } - case LUA_TTABLE: { - push_onecapture(ms, 0, s, e); + case LUA_TTABLE: { /* index the table */ + push_onecapture(ms, 0, s, e); /* first capture is the index */ lua_gettable(L, 3); break; } default: { /* LUA_TNUMBER or LUA_TSTRING */ - add_s(ms, b, s, e); - return; + add_s(ms, b, s, e); /* add value to the buffer */ + return 1; /* something changed */ } } if (!lua_toboolean(L, -1)) { /* nil or false? */ - lua_pop(L, 1); - lua_pushlstring(L, s, e - s); /* keep original text */ + lua_pop(L, 1); /* remove value */ + luaL_addlstring(b, s, e - s); /* keep original text */ + return 0; /* no changes */ + } + else if (l_unlikely(!lua_isstring(L, -1))) + return luaL_error(L, "invalid replacement value (a %s)", + luaL_typename(L, -1)); + else { + luaL_addvalue(b); /* add result to accumulator */ + return 1; /* something changed */ } - else if (!lua_isstring(L, -1)) - luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); - luaL_addvalue(b); /* add result to accumulator */ } static int str_gsub (lua_State *L) { size_t srcl, lp; - const char *src = luaL_checklstring(L, 1, &srcl); - const char *p = luaL_checklstring(L, 2, &lp); - int tr = lua_type(L, 3); - lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); + const char *src = luaL_checklstring(L, 1, &srcl); /* subject */ + const char *p = luaL_checklstring(L, 2, &lp); /* pattern */ + const char *lastmatch = NULL; /* end of last match */ + int tr = lua_type(L, 3); /* replacement type */ + lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); /* max replacements */ int anchor = (*p == '^'); - lua_Integer n = 0; + lua_Integer n = 0; /* replacement count */ + int changed = 0; /* change flag */ MatchState ms; luaL_Buffer b; - luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || + luaL_argexpected(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, - "string/function/table expected"); + "string/function/table"); luaL_buffinit(L, &b); if (anchor) { p++; lp--; /* skip anchor character */ } - ms.L = L; - ms.matchdepth = MAXCCALLS; - ms.src_init = src; - ms.src_end = src+srcl; - ms.p_end = p + lp; + prepstate(&ms, L, src, srcl, p, lp); while (n < max_s) { const char *e; - ms.level = 0; - lua_assert(ms.matchdepth == MAXCCALLS); - e = match(&ms, src, p); - if (e) { + reprepstate(&ms); /* (re)prepare state for new match */ + if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */ n++; - add_value(&ms, &b, src, e, tr); + changed = add_value(&ms, &b, src, e, tr) | changed; + src = lastmatch = e; } - if (e && e>src) /* non empty match? */ - src = e; /* skip it */ - else if (src < ms.src_end) + else if (src < ms.src_end) /* otherwise, skip one character */ luaL_addchar(&b, *src++); - else break; + else break; /* end of subject */ if (anchor) break; } - luaL_addlstring(&b, src, ms.src_end-src); - luaL_pushresult(&b); + if (!changed) /* no changes? */ + lua_pushvalue(L, 1); /* return original string */ + else { /* something changed */ + luaL_addlstring(&b, src, ms.src_end-src); + luaL_pushresult(&b); /* create and return new string */ + } lua_pushinteger(L, n); /* number of substitutions */ return 2; } @@ -804,9 +998,6 @@ static int str_gsub (lua_State *L) { ** Hexadecimal floating-point formatter */ -#include -#include - #define SIZELENMOD (sizeof(LUA_NUMBER_FRMLEN)/sizeof(char)) @@ -816,7 +1007,7 @@ static int str_gsub (lua_State *L) { ** to nibble boundaries by making what is left after that first digit a ** multiple of 4. */ -#define L_NBFD ((l_mathlim(MANT_DIG) - 1)%4 + 1) +#define L_NBFD ((l_floatatt(MANT_DIG) - 1)%4 + 1) /* @@ -830,20 +1021,20 @@ static lua_Number adddigit (char *buff, int n, lua_Number x) { } -static int num2straux (char *buff, lua_Number x) { - if (x != x || x == HUGE_VAL || x == -HUGE_VAL) /* inf or NaN? */ - return sprintf(buff, LUA_NUMBER_FMT, x); /* equal to '%g' */ +static int num2straux (char *buff, int sz, lua_Number x) { + /* if 'inf' or 'NaN', format it like '%g' */ + if (x != x || x == (lua_Number)HUGE_VAL || x == -(lua_Number)HUGE_VAL) + return l_sprintf(buff, sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)x); else if (x == 0) { /* can be -0... */ - sprintf(buff, LUA_NUMBER_FMT, x); - strcat(buff, "x0p+0"); /* reuses '0/-0' from 'sprintf'... */ - return strlen(buff); + /* create "0" or "-0" followed by exponent */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT "x0p+0", (LUAI_UACNUMBER)x); } else { int e; lua_Number m = l_mathop(frexp)(x, &e); /* 'x' fraction and exponent */ int n = 0; /* character count */ if (m < 0) { /* is number negative? */ - buff[n++] = '-'; /* add signal */ + buff[n++] = '-'; /* add sign */ m = -m; /* make it positive */ } buff[n++] = '0'; buff[n++] = 'x'; /* add "0x" */ @@ -855,22 +1046,23 @@ static int num2straux (char *buff, lua_Number x) { m = adddigit(buff, n++, m * 16); } while (m > 0); } - n += sprintf(buff + n, "p%+d", e); /* add exponent */ + n += l_sprintf(buff + n, sz - n, "p%+d", e); /* add exponent */ + lua_assert(n < sz); return n; } } -static int lua_number2strx (lua_State *L, char *buff, const char *fmt, - lua_Number x) { - int n = num2straux(buff, x); +static int lua_number2strx (lua_State *L, char *buff, int sz, + const char *fmt, lua_Number x) { + int n = num2straux(buff, sz, x); if (fmt[SIZELENMOD] == 'A') { int i; for (i = 0; i < n; i++) buff[i] = toupper(uchar(buff[i])); } - else if (fmt[SIZELENMOD] != 'a') - luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); + else if (l_unlikely(fmt[SIZELENMOD] != 'a')) + return luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); return n; } @@ -878,37 +1070,68 @@ static int lua_number2strx (lua_State *L, char *buff, const char *fmt, /* -** Maximum size of each formatted item. This maximum size is produced -** by format('%.99f', minfloat), and is equal to 99 + 2 ('-' and '.') + -** number of decimal digits to represent minfloat. +** Maximum size for items formatted with '%f'. This size is produced +** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.', +** and '\0') + number of decimal digits to represent maxfloat (which +** is maximum exponent + 1). (99+3+1, adding some extra, 110) +*/ +#define MAX_ITEMF (110 + l_floatatt(MAX_10_EXP)) + + +/* +** All formats except '%f' do not need that large limit. The other +** float formats use exponents, so that they fit in the 99 limit for +** significant digits; 's' for large strings and 'q' add items directly +** to the buffer; all integer formats also fit in the 99 limit. The +** worst case are floats: they may need 99 significant digits, plus +** '0x', '-', '.', 'e+XXXX', and '\0'. Adding some extra, 120. */ -#define MAX_ITEM (120 + l_mathlim(MAX_10_EXP)) +#define MAX_ITEM 120 /* valid flags in a format specification */ -#define FLAGS "-+ #0" +#if !defined(L_FMTFLAGSF) + +/* valid flags for a, A, e, E, f, F, g, and G conversions */ +#define L_FMTFLAGSF "-+#0 " + +/* valid flags for o, x, and X conversions */ +#define L_FMTFLAGSX "-#0" + +/* valid flags for d and i conversions */ +#define L_FMTFLAGSI "-+0 " + +/* valid flags for u conversions */ +#define L_FMTFLAGSU "-0" + +/* valid flags for c, p, and s conversions */ +#define L_FMTFLAGSC "-" + +#endif + /* -** maximum size of each format specification (such as "%-099.99d") +** Maximum size of each format specification (such as "%-099.99d"): +** Initial '%', flags (up to 5), width (2), period, precision (2), +** length modifier (8), conversion specifier, and final '\0', plus some +** extra. */ #define MAX_FORMAT 32 -static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { - size_t l; - const char *s = luaL_checklstring(L, arg, &l); +static void addquoted (luaL_Buffer *b, const char *s, size_t len) { luaL_addchar(b, '"'); - while (l--) { + while (len--) { if (*s == '"' || *s == '\\' || *s == '\n') { luaL_addchar(b, '\\'); luaL_addchar(b, *s); } - else if (*s == '\0' || iscntrl(uchar(*s))) { + else if (iscntrl(uchar(*s))) { char buff[10]; if (!isdigit(uchar(*(s+1)))) - sprintf(buff, "\\%d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%d", (int)uchar(*s)); else - sprintf(buff, "\\%03d", (int)uchar(*s)); + l_sprintf(buff, sizeof(buff), "\\%03d", (int)uchar(*s)); luaL_addstring(b, buff); } else @@ -918,25 +1141,119 @@ static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { luaL_addchar(b, '"'); } -static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { - const char *p = strfrmt; - while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ - if ((size_t)(p - strfrmt) >= sizeof(FLAGS)/sizeof(char)) - luaL_error(L, "invalid format (repeated flags)"); - if (isdigit(uchar(*p))) p++; /* skip width */ - if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ - if (*p == '.') { - p++; - if (isdigit(uchar(*p))) p++; /* skip precision */ - if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ + +/* +** Serialize a floating-point number in such a way that it can be +** scanned back by Lua. Use hexadecimal format for "common" numbers +** (to preserve precision); inf, -inf, and NaN are handled separately. +** (NaN cannot be expressed as a numeral, so we write '(0/0)' for it.) +*/ +static int quotefloat (lua_State *L, char *buff, lua_Number n) { + const char *s; /* for the fixed representations */ + if (n == (lua_Number)HUGE_VAL) /* inf? */ + s = "1e9999"; + else if (n == -(lua_Number)HUGE_VAL) /* -inf? */ + s = "-1e9999"; + else if (n != n) /* NaN? */ + s = "(0/0)"; + else { /* format number as hexadecimal */ + int nb = lua_number2strx(L, buff, MAX_ITEM, + "%" LUA_NUMBER_FRMLEN "a", n); + /* ensures that 'buff' string uses a dot as the radix character */ + if (memchr(buff, '.', nb) == NULL) { /* no dot? */ + char point = lua_getlocaledecpoint(); /* try locale point */ + char *ppoint = (char *)memchr(buff, point, nb); + if (ppoint) *ppoint = '.'; /* change it to a dot */ + } + return nb; + } + /* for the fixed representations */ + return l_sprintf(buff, MAX_ITEM, "%s", s); +} + + +static void addliteral (lua_State *L, luaL_Buffer *b, int arg) { + switch (lua_type(L, arg)) { + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(L, arg, &len); + addquoted(b, s, len); + break; + } + case LUA_TNUMBER: { + char *buff = luaL_prepbuffsize(b, MAX_ITEM); + int nb; + if (!lua_isinteger(L, arg)) /* float? */ + nb = quotefloat(L, buff, lua_tonumber(L, arg)); + else { /* integers */ + lua_Integer n = lua_tointeger(L, arg); + const char *format = (n == LUA_MININTEGER) /* corner case? */ + ? "0x%" LUA_INTEGER_FRMLEN "x" /* use hex */ + : LUA_INTEGER_FMT; /* else use default format */ + nb = l_sprintf(buff, MAX_ITEM, format, (LUAI_UACINT)n); + } + luaL_addsize(b, nb); + break; + } + case LUA_TNIL: case LUA_TBOOLEAN: { + luaL_tolstring(L, arg, NULL); + luaL_addvalue(b); + break; + } + default: { + luaL_argerror(L, arg, "value has no literal form"); + } } - if (isdigit(uchar(*p))) - luaL_error(L, "invalid format (width or precision too long)"); +} + + +static const char *get2digits (const char *s) { + if (isdigit(uchar(*s))) { + s++; + if (isdigit(uchar(*s))) s++; /* (2 digits at most) */ + } + return s; +} + + +/* +** Check whether a conversion specification is valid. When called, +** first character in 'form' must be '%' and last character must +** be a valid conversion specifier. 'flags' are the accepted flags; +** 'precision' signals whether to accept a precision. +*/ +static void checkformat (lua_State *L, const char *form, const char *flags, + int precision) { + const char *spec = form + 1; /* skip '%' */ + spec += strspn(spec, flags); /* skip flags */ + if (*spec != '0') { /* a width cannot start with '0' */ + spec = get2digits(spec); /* skip width */ + if (*spec == '.' && precision) { + spec++; + spec = get2digits(spec); /* skip precision */ + } + } + if (!isalpha(uchar(*spec))) /* did not go to the end? */ + luaL_error(L, "invalid conversion specification: '%s'", form); +} + + +/* +** Get a conversion specification and copy it to 'form'. +** Return the address of its last character. +*/ +static const char *getformat (lua_State *L, const char *strfrmt, + char *form) { + /* spans flags, width, and precision ('0' is included as a flag) */ + size_t len = strspn(strfrmt, L_FMTFLAGSF "123456789."); + len++; /* adds following character (should be the specifier) */ + /* still needs space for '%', '\0', plus a length modifier */ + if (len >= MAX_FORMAT - 10) + luaL_error(L, "invalid format (too long)"); *(form++) = '%'; - memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char)); - form += (p - strfrmt) + 1; - *form = '\0'; - return p; + memcpy(form, strfrmt, len * sizeof(char)); + *(form + len) = '\0'; + return strfrmt + len - 1; } @@ -959,6 +1276,7 @@ static int str_format (lua_State *L) { size_t sfl; const char *strfrmt = luaL_checklstring(L, arg, &sfl); const char *strfrmt_end = strfrmt+sfl; + const char *flags; luaL_Buffer b; luaL_buffinit(L, &b); while (strfrmt < strfrmt_end) { @@ -968,56 +1286,90 @@ static int str_format (lua_State *L) { luaL_addchar(&b, *strfrmt++); /* %% */ else { /* format item */ char form[MAX_FORMAT]; /* to store the format ('%...') */ - char *buff = luaL_prepbuffsize(&b, MAX_ITEM); /* to put formatted item */ - int nb = 0; /* number of bytes in added item */ + int maxitem = MAX_ITEM; /* maximum length for the result */ + char *buff = luaL_prepbuffsize(&b, maxitem); /* to put result */ + int nb = 0; /* number of bytes in result */ if (++arg > top) - luaL_argerror(L, arg, "no value"); - strfrmt = scanformat(L, strfrmt, form); + return luaL_argerror(L, arg, "no value"); + strfrmt = getformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { - nb = sprintf(buff, form, (int)luaL_checkinteger(L, arg)); + checkformat(L, form, L_FMTFLAGSC, 0); + nb = l_sprintf(buff, maxitem, form, (int)luaL_checkinteger(L, arg)); break; } case 'd': case 'i': - case 'o': case 'u': case 'x': case 'X': { + flags = L_FMTFLAGSI; + goto intcase; + case 'u': + flags = L_FMTFLAGSU; + goto intcase; + case 'o': case 'x': case 'X': + flags = L_FMTFLAGSX; + intcase: { lua_Integer n = luaL_checkinteger(L, arg); + checkformat(L, form, flags, 1); addlenmod(form, LUA_INTEGER_FRMLEN); - nb = sprintf(buff, form, n); + nb = l_sprintf(buff, maxitem, form, (LUAI_UACINT)n); break; } case 'a': case 'A': + checkformat(L, form, L_FMTFLAGSF, 1); addlenmod(form, LUA_NUMBER_FRMLEN); - nb = lua_number2strx(L, buff, form, luaL_checknumber(L, arg)); + nb = lua_number2strx(L, buff, maxitem, form, + luaL_checknumber(L, arg)); break; - case 'e': case 'E': case 'f': - case 'g': case 'G': { + case 'f': + maxitem = MAX_ITEMF; /* extra space for '%f' */ + buff = luaL_prepbuffsize(&b, maxitem); + /* FALLTHROUGH */ + case 'e': case 'E': case 'g': case 'G': { + lua_Number n = luaL_checknumber(L, arg); + checkformat(L, form, L_FMTFLAGSF, 1); addlenmod(form, LUA_NUMBER_FRMLEN); - nb = sprintf(buff, form, luaL_checknumber(L, arg)); + nb = l_sprintf(buff, maxitem, form, (LUAI_UACNUMBER)n); + break; + } + case 'p': { + const void *p = lua_topointer(L, arg); + checkformat(L, form, L_FMTFLAGSC, 0); + if (p == NULL) { /* avoid calling 'printf' with argument NULL */ + p = "(null)"; /* result */ + form[strlen(form) - 1] = 's'; /* format it as a string */ + } + nb = l_sprintf(buff, maxitem, form, p); break; } case 'q': { - addquoted(L, &b, arg); + if (form[2] != '\0') /* modifiers? */ + return luaL_error(L, "specifier '%%q' cannot have modifiers"); + addliteral(L, &b, arg); break; } case 's': { size_t l; const char *s = luaL_tolstring(L, arg, &l); - if (!strchr(form, '.') && l >= 100) { - /* no precision and string is too long to be formatted; - keep original string */ - luaL_addvalue(&b); - } + if (form[2] == '\0') /* no modifiers? */ + luaL_addvalue(&b); /* keep entire string */ else { - nb = sprintf(buff, form, s); - lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + luaL_argcheck(L, l == strlen(s), arg, "string contains zeros"); + checkformat(L, form, L_FMTFLAGSC, 1); + if (strchr(form, '.') == NULL && l >= 100) { + /* no precision and string is too long to be formatted */ + luaL_addvalue(&b); /* keep entire string */ + } + else { /* format the string into 'buff' */ + nb = l_sprintf(buff, maxitem, form, s); + lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + } } break; } default: { /* also treat cases 'pnLlh' */ - return luaL_error(L, "invalid option '%%%c' to 'format'", - *(strfrmt - 1)); + return luaL_error(L, "invalid conversion '%s' to 'format'", form); } } + lua_assert(nb < maxitem); luaL_addsize(&b, nb); } } @@ -1036,8 +1388,8 @@ static int str_format (lua_State *L) { /* value used for padding */ -#if !defined(LUA_PACKPADBYTE) -#define LUA_PACKPADBYTE 0x00 +#if !defined(LUAL_PACKPADBYTE) +#define LUAL_PACKPADBYTE 0x00 #endif /* maximum size for the binary representation of an integer */ @@ -1060,26 +1412,6 @@ static const union { } nativeendian = {1}; -/* dummy structure to get native alignment requirements */ -struct cD { - char c; - union { double d; void *p; lua_Integer i; lua_Number n; } u; -}; - -#define MAXALIGN (offsetof(struct cD, u)) - - -/* -** Union for serializing floats -*/ -typedef union Ftypes { - float f; - double d; - lua_Number n; - char buff[5 * sizeof(lua_Number)]; /* enough for any float type */ -} Ftypes; - - /* ** information to pack/unpack stuff */ @@ -1096,7 +1428,9 @@ typedef struct Header { typedef enum KOption { Kint, /* signed integers */ Kuint, /* unsigned integers */ - Kfloat, /* floating-point numbers */ + Kfloat, /* single-precision floating-point numbers */ + Knumber, /* Lua "native" floating-point numbers */ + Kdouble, /* double-precision floating-point numbers */ Kchar, /* fixed-length strings */ Kstring, /* strings with prefixed length */ Kzstr, /* zero-terminated strings */ @@ -1131,9 +1465,9 @@ static int getnum (const char **fmt, int df) { */ static int getnumlimit (Header *h, const char **fmt, int df) { int sz = getnum(fmt, df); - if (sz > MAXINTSIZE || sz <= 0) - luaL_error(h->L, "integral size (%d) out of limits [1,%d]", - sz, MAXINTSIZE); + if (l_unlikely(sz > MAXINTSIZE || sz <= 0)) + return luaL_error(h->L, "integral size (%d) out of limits [1,%d]", + sz, MAXINTSIZE); return sz; } @@ -1152,6 +1486,8 @@ static void initheader (lua_State *L, Header *h) { ** Read and classify next option. 'size' is filled with option's size. */ static KOption getoption (Header *h, const char **fmt, int *size) { + /* dummy structure to get native alignment requirements */ + struct cD { char c; union { LUAI_MAXALIGN; } u; }; int opt = *((*fmt)++); *size = 0; /* default */ switch (opt) { @@ -1165,14 +1501,14 @@ static KOption getoption (Header *h, const char **fmt, int *size) { case 'J': *size = sizeof(lua_Integer); return Kuint; case 'T': *size = sizeof(size_t); return Kuint; case 'f': *size = sizeof(float); return Kfloat; - case 'd': *size = sizeof(double); return Kfloat; - case 'n': *size = sizeof(lua_Number); return Kfloat; + case 'n': *size = sizeof(lua_Number); return Knumber; + case 'd': *size = sizeof(double); return Kdouble; case 'i': *size = getnumlimit(h, fmt, sizeof(int)); return Kint; case 'I': *size = getnumlimit(h, fmt, sizeof(int)); return Kuint; case 's': *size = getnumlimit(h, fmt, sizeof(size_t)); return Kstring; case 'c': *size = getnum(fmt, -1); - if (*size == -1) + if (l_unlikely(*size == -1)) luaL_error(h->L, "missing size for format option 'c'"); return Kchar; case 'z': return Kzstr; @@ -1182,7 +1518,11 @@ static KOption getoption (Header *h, const char **fmt, int *size) { case '<': h->islittle = 1; break; case '>': h->islittle = 0; break; case '=': h->islittle = nativeendian.little; break; - case '!': h->maxalign = getnumlimit(h, fmt, MAXALIGN); break; + case '!': { + const int maxalign = offsetof(struct cD, u); + h->maxalign = getnumlimit(h, fmt, maxalign); + break; + } default: luaL_error(h->L, "invalid format option '%c'", opt); } return Knop; @@ -1194,7 +1534,7 @@ static KOption getoption (Header *h, const char **fmt, int *size) { ** 'psize' is filled with option's size, 'notoalign' with its ** alignment requirements. ** Local variable 'size' gets the size to be aligned. (Kpadal option -** always gets its full alignment, other options are limited by +** always gets its full alignment, other options are limited by ** the maximum alignment ('maxalign'). Kchar option needs no alignment ** despite its size. */ @@ -1211,7 +1551,7 @@ static KOption getdetails (Header *h, size_t totalsize, else { if (align > h->maxalign) /* enforce maximum alignment */ align = h->maxalign; - if ((align & (align - 1)) != 0) /* is 'align' not a power of 2? */ + if (l_unlikely((align & (align - 1)) != 0)) /* not a power of 2? */ luaL_argerror(h->L, 1, "format asks for alignment not power of 2"); *ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1); } @@ -1246,12 +1586,10 @@ static void packint (luaL_Buffer *b, lua_Unsigned n, ** Copy 'size' bytes from 'src' to 'dest', correcting endianness if ** given 'islittle' is different from native endianness. */ -static void copywithendian (volatile char *dest, volatile const char *src, +static void copywithendian (char *dest, const char *src, int size, int islittle) { - if (islittle == nativeendian.little) { - while (size-- != 0) - *(dest++) = *(src++); - } + if (islittle == nativeendian.little) + memcpy(dest, src, size); else { dest += size - 1; while (size-- != 0) @@ -1274,7 +1612,7 @@ static int str_pack (lua_State *L) { KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); totalsize += ntoalign + size; while (ntoalign-- > 0) - luaL_addchar(&b, LUA_PACKPADBYTE); /* fill alignment */ + luaL_addchar(&b, LUAL_PACKPADBYTE); /* fill alignment */ arg++; switch (opt) { case Kint: { /* signed integers */ @@ -1294,23 +1632,38 @@ static int str_pack (lua_State *L) { packint(&b, (lua_Unsigned)n, h.islittle, size, 0); break; } - case Kfloat: { /* floating-point options */ - volatile Ftypes u; - char *buff = luaL_prepbuffsize(&b, size); - lua_Number n = luaL_checknumber(L, arg); /* get argument */ - if (size == sizeof(u.f)) u.f = (float)n; /* copy it into 'u' */ - else if (size == sizeof(u.d)) u.d = (double)n; - else u.n = n; - /* move 'u' to final result, correcting endianness if needed */ - copywithendian(buff, u.buff, size, h.islittle); + case Kfloat: { /* C float */ + float f = (float)luaL_checknumber(L, arg); /* get argument */ + char *buff = luaL_prepbuffsize(&b, sizeof(f)); + /* move 'f' to final result, correcting endianness if needed */ + copywithendian(buff, (char *)&f, sizeof(f), h.islittle); + luaL_addsize(&b, size); + break; + } + case Knumber: { /* Lua float */ + lua_Number f = luaL_checknumber(L, arg); /* get argument */ + char *buff = luaL_prepbuffsize(&b, sizeof(f)); + /* move 'f' to final result, correcting endianness if needed */ + copywithendian(buff, (char *)&f, sizeof(f), h.islittle); + luaL_addsize(&b, size); + break; + } + case Kdouble: { /* C double */ + double f = (double)luaL_checknumber(L, arg); /* get argument */ + char *buff = luaL_prepbuffsize(&b, sizeof(f)); + /* move 'f' to final result, correcting endianness if needed */ + copywithendian(buff, (char *)&f, sizeof(f), h.islittle); luaL_addsize(&b, size); break; } case Kchar: { /* fixed-size string */ size_t len; const char *s = luaL_checklstring(L, arg, &len); - luaL_argcheck(L, len == (size_t)size, arg, "wrong length"); - luaL_addlstring(&b, s, size); + luaL_argcheck(L, len <= (size_t)size, arg, + "string longer than given size"); + luaL_addlstring(&b, s, len); /* add string */ + while (len++ < (size_t)size) /* pad extra space */ + luaL_addchar(&b, LUAL_PACKPADBYTE); break; } case Kstring: { /* strings with length count */ @@ -1333,7 +1686,7 @@ static int str_pack (lua_State *L) { totalsize += len + 1; break; } - case Kpadding: luaL_addchar(&b, LUA_PACKPADBYTE); /* FALLTHROUGH */ + case Kpadding: luaL_addchar(&b, LUAL_PACKPADBYTE); /* FALLTHROUGH */ case Kpaddalign: case Knop: arg--; /* undo increment */ break; @@ -1352,17 +1705,12 @@ static int str_packsize (lua_State *L) { while (*fmt != '\0') { int size, ntoalign; KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1, + "variable-length format"); size += ntoalign; /* total space used by option */ luaL_argcheck(L, totalsize <= MAXSIZE - size, 1, "format result too large"); totalsize += size; - switch (opt) { - case Kstring: /* strings with length count */ - case Kzstr: /* zero-terminated string */ - luaL_argerror(L, 1, "variable-length format"); - break; - default: break; - } } lua_pushinteger(L, (lua_Integer)totalsize); return 1; @@ -1395,7 +1743,7 @@ static lua_Integer unpackint (lua_State *L, const char *str, else if (size > SZINT) { /* must check unread bytes */ int mask = (!issigned || (lua_Integer)res >= 0) ? 0 : MC; for (i = limit; i < size; i++) { - if ((unsigned char)str[islittle ? i : size - 1 - i] != mask) + if (l_unlikely((unsigned char)str[islittle ? i : size - 1 - i] != mask)) luaL_error(L, "%d-byte integer does not fit into Lua Integer", size); } } @@ -1408,15 +1756,15 @@ static int str_unpack (lua_State *L) { const char *fmt = luaL_checkstring(L, 1); size_t ld; const char *data = luaL_checklstring(L, 2, &ld); - size_t pos = (size_t)posrelat(luaL_optinteger(L, 3, 1), ld) - 1; + size_t pos = posrelatI(luaL_optinteger(L, 3, 1), ld) - 1; int n = 0; /* number of results */ luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); initheader(L, &h); while (*fmt != '\0') { int size, ntoalign; KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign); - if ((size_t)ntoalign + size > ~pos || pos + ntoalign + size > ld) - luaL_argerror(L, 2, "data string too short"); + luaL_argcheck(L, (size_t)ntoalign + size <= ld - pos, 2, + "data string too short"); pos += ntoalign; /* skip alignment */ /* stack space for item + next position */ luaL_checkstack(L, 2, "too many results"); @@ -1430,13 +1778,21 @@ static int str_unpack (lua_State *L) { break; } case Kfloat: { - volatile Ftypes u; - lua_Number num; - copywithendian(u.buff, data + pos, size, h.islittle); - if (size == sizeof(u.f)) num = (lua_Number)u.f; - else if (size == sizeof(u.d)) num = (lua_Number)u.d; - else num = u.n; - lua_pushnumber(L, num); + float f; + copywithendian((char *)&f, data + pos, sizeof(f), h.islittle); + lua_pushnumber(L, (lua_Number)f); + break; + } + case Knumber: { + lua_Number f; + copywithendian((char *)&f, data + pos, sizeof(f), h.islittle); + lua_pushnumber(L, f); + break; + } + case Kdouble: { + double f; + copywithendian((char *)&f, data + pos, sizeof(f), h.islittle); + lua_pushnumber(L, (lua_Number)f); break; } case Kchar: { @@ -1445,13 +1801,15 @@ static int str_unpack (lua_State *L) { } case Kstring: { size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0); - luaL_argcheck(L, pos + len + size <= ld, 2, "data string too short"); + luaL_argcheck(L, len <= ld - pos - size, 2, "data string too short"); lua_pushlstring(L, data + pos + size, len); pos += len; /* skip string */ break; } case Kzstr: { - size_t len = (int)strlen(data + pos); + size_t len = strlen(data + pos); + luaL_argcheck(L, pos + len < ld, 2, + "unfinished string for format 'z'"); lua_pushlstring(L, data + pos, len); pos += len + 1; /* skip string plus final '\0' */ break; @@ -1492,7 +1850,9 @@ static const luaL_Reg strlib[] = { static void createmetatable (lua_State *L) { - lua_createtable(L, 0, 1); /* table to be metatable for strings */ + /* table to be metatable for strings */ + luaL_newlibtable(L, stringmetamethods); + luaL_setfuncs(L, stringmetamethods, 0); lua_pushliteral(L, ""); /* dummy string */ lua_pushvalue(L, -2); /* copy table */ lua_setmetatable(L, -2); /* set table as metatable for strings */ diff --git a/libs/lua/ltable.c b/libs/lua/ltable.c index 04f2a347..3353c047 100644 --- a/libs/lua/ltable.c +++ b/libs/lua/ltable.c @@ -1,5 +1,5 @@ /* -** $Id: ltable.c,v 2.111 2015/06/09 14:21:13 roberto Exp $ +** $Id: ltable.c $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ @@ -40,52 +40,84 @@ /* -** Maximum size of array part (MAXASIZE) is 2^MAXABITS. MAXABITS is -** the largest integer such that MAXASIZE fits in an unsigned int. +** MAXABITS is the largest integer such that MAXASIZE fits in an +** unsigned int. */ #define MAXABITS cast_int(sizeof(int) * CHAR_BIT - 1) -#define MAXASIZE (1u << MAXABITS) + /* -** Maximum size of hash part is 2^MAXHBITS. MAXHBITS is the largest -** integer such that 2^MAXHBITS fits in a signed int. (Note that the -** maximum number of elements in a table, 2^MAXABITS + 2^MAXHBITS, still -** fits comfortably in an unsigned int.) +** MAXASIZE is the maximum size of the array part. It is the minimum +** between 2^MAXABITS and the maximum size that, measured in bytes, +** fits in a 'size_t'. +*/ +#define MAXASIZE luaM_limitN(1u << MAXABITS, TValue) + +/* +** MAXHBITS is the largest integer such that 2^MAXHBITS fits in a +** signed int. */ #define MAXHBITS (MAXABITS - 1) -#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) +/* +** MAXHSIZE is the maximum size of the hash part. It is the minimum +** between 2^MAXHBITS and the maximum size such that, measured in bytes, +** it fits in a 'size_t'. +*/ +#define MAXHSIZE luaM_limitN(1u << MAXHBITS, Node) -#define hashstr(t,str) hashpow2(t, (str)->hash) -#define hashboolean(t,p) hashpow2(t, p) -#define hashint(t,i) hashpow2(t, i) +/* +** When the original hash value is good, hashing by a power of 2 +** avoids the cost of '%'. +*/ +#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) /* -** for some types, it is better to avoid modulus by power of 2, as -** they tend to have many 2 factors. +** for other types, it is better to avoid modulo by power of 2, as +** they can have many 2 factors. */ #define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) +#define hashstr(t,str) hashpow2(t, (str)->hash) +#define hashboolean(t,p) hashpow2(t, p) + + #define hashpointer(t,p) hashmod(t, point2uint(p)) #define dummynode (&dummynode_) -#define isdummy(n) ((n) == dummynode) - static const Node dummynode_ = { - {NILCONSTANT}, /* value */ - {{NILCONSTANT, 0}} /* key */ + {{NULL}, LUA_VEMPTY, /* value's value and type */ + LUA_VNIL, 0, {NULL}} /* key type, next, and key value */ }; +static const TValue absentkey = {ABSTKEYCONSTANT}; + + +/* +** Hash for integers. To allow a good hash, use the remainder operator +** ('%'). If integer fits as a non-negative int, compute an int +** remainder, which is faster. Otherwise, use an unsigned-integer +** remainder, which uses all bits and ensures a non-negative result. +*/ +static Node *hashint (const Table *t, lua_Integer i) { + lua_Unsigned ui = l_castS2U(i); + if (ui <= cast_uint(INT_MAX)) + return hashmod(t, cast_int(ui)); + else + return hashmod(t, ui); +} + + /* ** Hash for floating-point numbers. ** The main computation should be just -** n = frepx(n, &i); return (n * INT_MAX) + i +** n = frexp(n, &i); return (n * INT_MAX) + i ** but there are some numerical subtleties. ** In a two-complement representation, INT_MAX does not has an exact ** representation as a float, but INT_MIN does; because the absolute @@ -101,60 +133,193 @@ static int l_hashfloat (lua_Number n) { lua_Integer ni; n = l_mathop(frexp)(n, &i) * -cast_num(INT_MIN); if (!lua_numbertointeger(n, &ni)) { /* is 'n' inf/-inf/NaN? */ - lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == HUGE_VAL); + lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == cast_num(HUGE_VAL)); return 0; } else { /* normal case */ - unsigned int u = cast(unsigned int, i) + cast(unsigned int, ni); - return cast_int(u <= cast(unsigned int, INT_MAX) ? u : ~u); + unsigned int u = cast_uint(i) + cast_uint(ni); + return cast_int(u <= cast_uint(INT_MAX) ? u : ~u); } } #endif /* -** returns the 'main' position of an element in a table (that is, the index -** of its hash value) +** returns the 'main' position of an element in a table (that is, +** the index of its hash value). */ -static Node *mainposition (const Table *t, const TValue *key) { - switch (ttype(key)) { - case LUA_TNUMINT: - return hashint(t, ivalue(key)); - case LUA_TNUMFLT: - return hashmod(t, l_hashfloat(fltvalue(key))); - case LUA_TSHRSTR: - return hashstr(t, tsvalue(key)); - case LUA_TLNGSTR: { - TString *s = tsvalue(key); - if (s->extra == 0) { /* no hash? */ - s->hash = luaS_hash(getstr(s), s->u.lnglen, s->hash); - s->extra = 1; /* now it has its hash */ - } - return hashstr(t, tsvalue(key)); +static Node *mainpositionTV (const Table *t, const TValue *key) { + switch (ttypetag(key)) { + case LUA_VNUMINT: { + lua_Integer i = ivalue(key); + return hashint(t, i); + } + case LUA_VNUMFLT: { + lua_Number n = fltvalue(key); + return hashmod(t, l_hashfloat(n)); + } + case LUA_VSHRSTR: { + TString *ts = tsvalue(key); + return hashstr(t, ts); + } + case LUA_VLNGSTR: { + TString *ts = tsvalue(key); + return hashpow2(t, luaS_hashlongstr(ts)); + } + case LUA_VFALSE: + return hashboolean(t, 0); + case LUA_VTRUE: + return hashboolean(t, 1); + case LUA_VLIGHTUSERDATA: { + void *p = pvalue(key); + return hashpointer(t, p); } - case LUA_TBOOLEAN: - return hashboolean(t, bvalue(key)); - case LUA_TLIGHTUSERDATA: - return hashpointer(t, pvalue(key)); - case LUA_TLCF: - return hashpointer(t, fvalue(key)); + case LUA_VLCF: { + lua_CFunction f = fvalue(key); + return hashpointer(t, f); + } + default: { + GCObject *o = gcvalue(key); + return hashpointer(t, o); + } + } +} + + +l_sinline Node *mainpositionfromnode (const Table *t, Node *nd) { + TValue key; + getnodekey(cast(lua_State *, NULL), &key, nd); + return mainpositionTV(t, &key); +} + + +/* +** Check whether key 'k1' is equal to the key in node 'n2'. This +** equality is raw, so there are no metamethods. Floats with integer +** values have been normalized, so integers cannot be equal to +** floats. It is assumed that 'eqshrstr' is simply pointer equality, so +** that short strings are handled in the default case. +** A true 'deadok' means to accept dead keys as equal to their original +** values. All dead keys are compared in the default case, by pointer +** identity. (Only collectable objects can produce dead keys.) Note that +** dead long strings are also compared by identity. +** Once a key is dead, its corresponding value may be collected, and +** then another value can be created with the same address. If this +** other value is given to 'next', 'equalkey' will signal a false +** positive. In a regular traversal, this situation should never happen, +** as all keys given to 'next' came from the table itself, and therefore +** could not have been collected. Outside a regular traversal, we +** have garbage in, garbage out. What is relevant is that this false +** positive does not break anything. (In particular, 'next' will return +** some other valid item on the table or nil.) +*/ +static int equalkey (const TValue *k1, const Node *n2, int deadok) { + if ((rawtt(k1) != keytt(n2)) && /* not the same variants? */ + !(deadok && keyisdead(n2) && iscollectable(k1))) + return 0; /* cannot be same key */ + switch (keytt(n2)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: + return 1; + case LUA_VNUMINT: + return (ivalue(k1) == keyival(n2)); + case LUA_VNUMFLT: + return luai_numeq(fltvalue(k1), fltvalueraw(keyval(n2))); + case LUA_VLIGHTUSERDATA: + return pvalue(k1) == pvalueraw(keyval(n2)); + case LUA_VLCF: + return fvalue(k1) == fvalueraw(keyval(n2)); + case ctb(LUA_VLNGSTR): + return luaS_eqlngstr(tsvalue(k1), keystrval(n2)); default: - return hashpointer(t, gcvalue(key)); + return gcvalue(k1) == gcvalueraw(keyval(n2)); } } /* -** returns the index for 'key' if 'key' is an appropriate key to live in -** the array part of the table, 0 otherwise. +** True if value of 'alimit' is equal to the real size of the array +** part of table 't'. (Otherwise, the array part must be larger than +** 'alimit'.) +*/ +#define limitequalsasize(t) (isrealasize(t) || ispow2((t)->alimit)) + + +/* +** Returns the real size of the 'array' array */ -static unsigned int arrayindex (const TValue *key) { - if (ttisinteger(key)) { - lua_Integer k = ivalue(key); - if (0 < k && (lua_Unsigned)k <= MAXASIZE) - return cast(unsigned int, k); /* 'key' is an appropriate array index */ +LUAI_FUNC unsigned int luaH_realasize (const Table *t) { + if (limitequalsasize(t)) + return t->alimit; /* this is the size */ + else { + unsigned int size = t->alimit; + /* compute the smallest power of 2 not smaller than 'size' */ + size |= (size >> 1); + size |= (size >> 2); + size |= (size >> 4); + size |= (size >> 8); +#if (UINT_MAX >> 14) > 3 /* unsigned int has more than 16 bits */ + size |= (size >> 16); +#if (UINT_MAX >> 30) > 3 + size |= (size >> 32); /* unsigned int has more than 32 bits */ +#endif +#endif + size++; + lua_assert(ispow2(size) && size/2 < t->alimit && t->alimit < size); + return size; } - return 0; /* 'key' did not match some condition */ +} + + +/* +** Check whether real size of the array is a power of 2. +** (If it is not, 'alimit' cannot be changed to any other value +** without changing the real size.) +*/ +static int ispow2realasize (const Table *t) { + return (!isrealasize(t) || ispow2(t->alimit)); +} + + +static unsigned int setlimittosize (Table *t) { + t->alimit = luaH_realasize(t); + setrealasize(t); + return t->alimit; +} + + +#define limitasasize(t) check_exp(isrealasize(t), t->alimit) + + + +/* +** "Generic" get version. (Not that generic: not valid for integers, +** which may be in array part, nor for floats with integral values.) +** See explanation about 'deadok' in function 'equalkey'. +*/ +static const TValue *getgeneric (Table *t, const TValue *key, int deadok) { + Node *n = mainpositionTV(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (equalkey(key, n, deadok)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return &absentkey; /* not found */ + n += nx; + } + } +} + + +/* +** returns the index for 'k' if 'k' is an appropriate key to live in +** the array part of a table, 0 otherwise. +*/ +static unsigned int arrayindex (lua_Integer k) { + if (l_castS2U(k) - 1u < MAXASIZE) /* 'k' in [1, MAXASIZE]? */ + return cast_uint(k); /* 'key' is an appropriate array index */ + else + return 0; } @@ -163,46 +328,39 @@ static unsigned int arrayindex (const TValue *key) { ** elements in the array part, then elements in the hash part. The ** beginning of a traversal is signaled by 0. */ -static unsigned int findindex (lua_State *L, Table *t, StkId key) { +static unsigned int findindex (lua_State *L, Table *t, TValue *key, + unsigned int asize) { unsigned int i; if (ttisnil(key)) return 0; /* first iteration */ - i = arrayindex(key); - if (i != 0 && i <= t->sizearray) /* is 'key' inside array part? */ + i = ttisinteger(key) ? arrayindex(ivalue(key)) : 0; + if (i - 1u < asize) /* is 'key' inside array part? */ return i; /* yes; that's the index */ else { - int nx; - Node *n = mainposition(t, key); - for (;;) { /* check whether 'key' is somewhere in the chain */ - /* key may be dead already, but it is ok to use it in 'next' */ - if (luaV_rawequalobj(gkey(n), key) || - (ttisdeadkey(gkey(n)) && iscollectable(key) && - deadvalue(gkey(n)) == gcvalue(key))) { - i = cast_int(n - gnode(t, 0)); /* key index in hash table */ - /* hash elements are numbered after array ones */ - return (i + 1) + t->sizearray; - } - nx = gnext(n); - if (nx == 0) - luaG_runerror(L, "invalid key to 'next'"); /* key not found */ - else n += nx; - } + const TValue *n = getgeneric(t, key, 1); + if (l_unlikely(isabstkey(n))) + luaG_runerror(L, "invalid key to 'next'"); /* key not found */ + i = cast_int(nodefromval(n) - gnode(t, 0)); /* key index in hash table */ + /* hash elements are numbered after array ones */ + return (i + 1) + asize; } } int luaH_next (lua_State *L, Table *t, StkId key) { - unsigned int i = findindex(L, t, key); /* find original element */ - for (; i < t->sizearray; i++) { /* try first array part */ - if (!ttisnil(&t->array[i])) { /* a non-nil value? */ - setivalue(key, i + 1); - setobj2s(L, key+1, &t->array[i]); + unsigned int asize = luaH_realasize(t); + unsigned int i = findindex(L, t, s2v(key), asize); /* find original key */ + for (; i < asize; i++) { /* try first array part */ + if (!isempty(&t->array[i])) { /* a non-empty entry? */ + setivalue(s2v(key), i + 1); + setobj2s(L, key + 1, &t->array[i]); return 1; } } - for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) { /* hash part */ - if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ - setobj2s(L, key, gkey(gnode(t, i))); - setobj2s(L, key+1, gval(gnode(t, i))); + for (i -= asize; cast_int(i) < sizenode(t); i++) { /* hash part */ + if (!isempty(gval(gnode(t, i)))) { /* a non-empty entry? */ + Node *n = gnode(t, i); + getnodekey(L, s2v(key), n); + setobj2s(L, key + 1, gval(n)); return 1; } } @@ -210,6 +368,12 @@ int luaH_next (lua_State *L, Table *t, StkId key) { } +static void freehash (lua_State *L, Table *t) { + if (!isdummy(t)) + luaM_freearray(L, t->node, cast_sizet(sizenode(t))); +} + + /* ** {============================================================= ** Rehash @@ -221,7 +385,8 @@ int luaH_next (lua_State *L, Table *t, StkId key) { ** "count array" where 'nums[i]' is the number of integers in the table ** between 2^(i - 1) + 1 and 2^i. 'pna' enters with the total number of ** integer keys in the table and leaves with the number of keys that -** will go to the array part; return the optimal size. +** will go to the array part; return the optimal size. (The condition +** 'twotoi > 0' in the for loop stops the loop if 'twotoi' overflows.) */ static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { int i; @@ -230,13 +395,13 @@ static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { unsigned int na = 0; /* number of elements to go to array part */ unsigned int optimal = 0; /* optimal size for array part */ /* loop while keys can fill more than half of total size */ - for (i = 0, twotoi = 1; *pna > twotoi / 2; i++, twotoi *= 2) { - if (nums[i] > 0) { - a += nums[i]; - if (a > twotoi/2) { /* more than half elements present? */ - optimal = twotoi; /* optimal size (till now) */ - na = a; /* all elements up to 'optimal' will go to array part */ - } + for (i = 0, twotoi = 1; + twotoi > 0 && *pna > twotoi / 2; + i++, twotoi *= 2) { + a += nums[i]; + if (a > twotoi/2) { /* more than half elements present? */ + optimal = twotoi; /* optimal size (till now) */ + na = a; /* all elements up to 'optimal' will go to array part */ } } lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal); @@ -245,7 +410,7 @@ static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { } -static int countint (const TValue *key, unsigned int *nums) { +static int countint (lua_Integer key, unsigned int *nums) { unsigned int k = arrayindex(key); if (k != 0) { /* is 'key' an appropriate array index? */ nums[luaO_ceillog2(k)]++; /* count as such */ @@ -266,18 +431,19 @@ static unsigned int numusearray (const Table *t, unsigned int *nums) { unsigned int ttlg; /* 2^lg */ unsigned int ause = 0; /* summation of 'nums' */ unsigned int i = 1; /* count to traverse all array keys */ + unsigned int asize = limitasasize(t); /* real array size */ /* traverse each slice */ for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) { unsigned int lc = 0; /* counter */ unsigned int lim = ttlg; - if (lim > t->sizearray) { - lim = t->sizearray; /* adjust upper limit */ + if (lim > asize) { + lim = asize; /* adjust upper limit */ if (i > lim) break; /* no more elements to count */ } /* count elements in range (2^(lg - 1), 2^lg] */ for (; i <= lim; i++) { - if (!ttisnil(&t->array[i-1])) + if (!isempty(&t->array[i-1])) lc++; } nums[lg] += lc; @@ -293,8 +459,9 @@ static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) { int i = sizenode(t); while (i--) { Node *n = &t->node[i]; - if (!ttisnil(gval(n))) { - ause += countint(gkey(n), nums); + if (!isempty(gval(n))) { + if (keyisinteger(n)) + ause += countint(keyival(n), nums); totaluse++; } } @@ -303,77 +470,125 @@ static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) { } -static void setarrayvector (lua_State *L, Table *t, unsigned int size) { - unsigned int i; - luaM_reallocvector(L, t->array, t->sizearray, size, TValue); - for (i=t->sizearray; iarray[i]); - t->sizearray = size; -} - - +/* +** Creates an array for the hash part of a table with the given +** size, or reuses the dummy node if size is zero. +** The computation for size overflow is in two steps: the first +** comparison ensures that the shift in the second one does not +** overflow. +*/ static void setnodevector (lua_State *L, Table *t, unsigned int size) { - int lsize; if (size == 0) { /* no elements to hash part? */ t->node = cast(Node *, dummynode); /* use common 'dummynode' */ - lsize = 0; + t->lsizenode = 0; + t->lastfree = NULL; /* signal that it is using dummy node */ } else { int i; - lsize = luaO_ceillog2(size); - if (lsize > MAXHBITS) + int lsize = luaO_ceillog2(size); + if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE) luaG_runerror(L, "table overflow"); size = twoto(lsize); t->node = luaM_newvector(L, size, Node); - for (i = 0; i < (int)size; i++) { + for (i = 0; i < cast_int(size); i++) { Node *n = gnode(t, i); gnext(n) = 0; - setnilvalue(wgkey(n)); - setnilvalue(gval(n)); + setnilkey(n); + setempty(gval(n)); } + t->lsizenode = cast_byte(lsize); + t->lastfree = gnode(t, size); /* all positions are free */ } - t->lsizenode = cast_byte(lsize); - t->lastfree = gnode(t, size); /* all positions are free */ } -void luaH_resize (lua_State *L, Table *t, unsigned int nasize, +/* +** (Re)insert all elements from the hash part of 'ot' into table 't'. +*/ +static void reinsert (lua_State *L, Table *ot, Table *t) { + int j; + int size = sizenode(ot); + for (j = 0; j < size; j++) { + Node *old = gnode(ot, j); + if (!isempty(gval(old))) { + /* doesn't need barrier/invalidate cache, as entry was + already present in the table */ + TValue k; + getnodekey(L, &k, old); + luaH_set(L, t, &k, gval(old)); + } + } +} + + +/* +** Exchange the hash part of 't1' and 't2'. +*/ +static void exchangehashpart (Table *t1, Table *t2) { + lu_byte lsizenode = t1->lsizenode; + Node *node = t1->node; + Node *lastfree = t1->lastfree; + t1->lsizenode = t2->lsizenode; + t1->node = t2->node; + t1->lastfree = t2->lastfree; + t2->lsizenode = lsizenode; + t2->node = node; + t2->lastfree = lastfree; +} + + +/* +** Resize table 't' for the new given sizes. Both allocations (for +** the hash part and for the array part) can fail, which creates some +** subtleties. If the first allocation, for the hash part, fails, an +** error is raised and that is it. Otherwise, it copies the elements from +** the shrinking part of the array (if it is shrinking) into the new +** hash. Then it reallocates the array part. If that fails, the table +** is in its original state; the function frees the new hash part and then +** raises the allocation error. Otherwise, it sets the new hash part +** into the table, initializes the new part of the array (if any) with +** nils and reinserts the elements of the old hash back into the new +** parts of the table. +*/ +void luaH_resize (lua_State *L, Table *t, unsigned int newasize, unsigned int nhsize) { unsigned int i; - int j; - unsigned int oldasize = t->sizearray; - int oldhsize = t->lsizenode; - Node *nold = t->node; /* save old hash ... */ - if (nasize > oldasize) /* array part must grow? */ - setarrayvector(L, t, nasize); - /* create new hash part with appropriate size */ - setnodevector(L, t, nhsize); - if (nasize < oldasize) { /* array part must shrink? */ - t->sizearray = nasize; - /* re-insert elements from vanishing slice */ - for (i=nasize; iarray[i])) + Table newt; /* to keep the new hash part */ + unsigned int oldasize = setlimittosize(t); + TValue *newarray; + /* create new hash part with appropriate size into 'newt' */ + setnodevector(L, &newt, nhsize); + if (newasize < oldasize) { /* will array shrink? */ + t->alimit = newasize; /* pretend array has new size... */ + exchangehashpart(t, &newt); /* and new hash */ + /* re-insert into the new hash the elements from vanishing slice */ + for (i = newasize; i < oldasize; i++) { + if (!isempty(&t->array[i])) luaH_setint(L, t, i + 1, &t->array[i]); } - /* shrink array */ - luaM_reallocvector(L, t->array, oldasize, nasize, TValue); + t->alimit = oldasize; /* restore current size... */ + exchangehashpart(t, &newt); /* and hash (in case of errors) */ } - /* re-insert elements from hash part */ - for (j = twoto(oldhsize) - 1; j >= 0; j--) { - Node *old = nold + j; - if (!ttisnil(gval(old))) { - /* doesn't need barrier/invalidate cache, as entry was - already present in the table */ - setobjt2t(L, luaH_set(L, t, gkey(old)), gval(old)); - } + /* allocate new array */ + newarray = luaM_reallocvector(L, t->array, oldasize, newasize, TValue); + if (l_unlikely(newarray == NULL && newasize > 0)) { /* allocation failed? */ + freehash(L, &newt); /* release new hash part */ + luaM_error(L); /* raise error (with array unchanged) */ } - if (!isdummy(nold)) - luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old hash */ + /* allocation ok; initialize new part of the array */ + exchangehashpart(t, &newt); /* 't' has the new hash ('newt' has the old) */ + t->array = newarray; /* set new array part */ + t->alimit = newasize; + for (i = oldasize; i < newasize; i++) /* clear new slice of the array */ + setempty(&t->array[i]); + /* re-insert elements from old hash part into new parts */ + reinsert(L, &newt, t); /* 'newt' now has the old hash */ + freehash(L, &newt); /* free old hash part */ } void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize) { - int nsize = isdummy(t->node) ? 0 : sizenode(t); + int nsize = allocsizenode(t); luaH_resize(L, t, nasize, nsize); } @@ -387,11 +602,13 @@ static void rehash (lua_State *L, Table *t, const TValue *ek) { int i; int totaluse; for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */ + setlimittosize(t); na = numusearray(t, nums); /* count keys in array part */ totaluse = na; /* all those keys are integer keys */ totaluse += numusehash(t, nums, &na); /* count keys in hash part */ /* count extra key */ - na += countint(ek, nums); + if (ttisinteger(ek)) + na += countint(ivalue(ek), nums); totaluse++; /* compute new size for array part */ asize = computesizes(nums, &na); @@ -407,30 +624,31 @@ static void rehash (lua_State *L, Table *t, const TValue *ek) { Table *luaH_new (lua_State *L) { - GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table)); + GCObject *o = luaC_newobj(L, LUA_VTABLE, sizeof(Table)); Table *t = gco2t(o); t->metatable = NULL; - t->flags = cast_byte(~0); + t->flags = cast_byte(maskflags); /* table has no metamethod fields */ t->array = NULL; - t->sizearray = 0; + t->alimit = 0; setnodevector(L, t, 0); return t; } void luaH_free (lua_State *L, Table *t) { - if (!isdummy(t->node)) - luaM_freearray(L, t->node, cast(size_t, sizenode(t))); - luaM_freearray(L, t->array, t->sizearray); + freehash(L, t); + luaM_freearray(L, t->array, luaH_realasize(t)); luaM_free(L, t); } static Node *getfreepos (Table *t) { - while (t->lastfree > t->node) { - t->lastfree--; - if (ttisnil(gkey(t->lastfree))) - return t->lastfree; + if (!isdummy(t)) { + while (t->lastfree > t->node) { + t->lastfree--; + if (keyisnil(t->lastfree)) + return t->lastfree; + } } return NULL; /* could not find a free place */ } @@ -444,30 +662,36 @@ static Node *getfreepos (Table *t) { ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ -TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { +static void luaH_newkey (lua_State *L, Table *t, const TValue *key, + TValue *value) { Node *mp; TValue aux; - if (ttisnil(key)) luaG_runerror(L, "table index is nil"); + if (l_unlikely(ttisnil(key))) + luaG_runerror(L, "table index is nil"); else if (ttisfloat(key)) { + lua_Number f = fltvalue(key); lua_Integer k; - if (luaV_tointeger(key, &k, 0)) { /* index is int? */ + if (luaV_flttointeger(f, &k, F2Ieq)) { /* does key fit in an integer? */ setivalue(&aux, k); key = &aux; /* insert it as an integer */ } - else if (luai_numisnan(fltvalue(key))) + else if (l_unlikely(luai_numisnan(f))) luaG_runerror(L, "table index is NaN"); } - mp = mainposition(t, key); - if (!ttisnil(gval(mp)) || isdummy(mp)) { /* main position is taken? */ + if (ttisnil(value)) + return; /* do not insert nil values */ + mp = mainpositionTV(t, key); + if (!isempty(gval(mp)) || isdummy(t)) { /* main position is taken? */ Node *othern; Node *f = getfreepos(t); /* get a free place */ if (f == NULL) { /* cannot find a free place? */ rehash(L, t, key); /* grow table */ - /* whatever called 'newkey' takes care of TM cache and GC barrier */ - return luaH_set(L, t, key); /* insert key into grown table */ + /* whatever called 'newkey' takes care of TM cache */ + luaH_set(L, t, key, value); /* insert key into grown table */ + return; } - lua_assert(!isdummy(f)); - othern = mainposition(t, gkey(mp)); + lua_assert(!isdummy(t)); + othern = mainpositionfromnode(t, mp); if (othern != mp) { /* is colliding node out of its main position? */ /* yes; move colliding node into free position */ while (othern + gnext(othern) != mp) /* find previous */ @@ -478,7 +702,7 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { gnext(f) += cast_int(mp - f); /* correct 'next' */ gnext(mp) = 0; /* now 'mp' is free */ } - setnilvalue(gval(mp)); + setempty(gval(mp)); } else { /* colliding node is in its own main position */ /* new node will go into free position */ @@ -489,32 +713,56 @@ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { mp = f; } } - setnodekey(L, &mp->i_key, key); - luaC_barrierback(L, t, key); - lua_assert(ttisnil(gval(mp))); - return gval(mp); + setnodekey(L, mp, key); + luaC_barrierback(L, obj2gco(t), key); + lua_assert(isempty(gval(mp))); + setobj2t(L, gval(mp), value); } /* -** search function for integers +** Search function for integers. If integer is inside 'alimit', get it +** directly from the array part. Otherwise, if 'alimit' is not +** the real size of the array, the key still can be in the array part. +** In this case, do the "Xmilia trick" to check whether 'key-1' is +** smaller than the real size. +** The trick works as follow: let 'p' be an integer such that +** '2^(p+1) >= alimit > 2^p', or '2^(p+1) > alimit-1 >= 2^p'. +** That is, 2^(p+1) is the real size of the array, and 'p' is the highest +** bit on in 'alimit-1'. What we have to check becomes 'key-1 < 2^(p+1)'. +** We compute '(key-1) & ~(alimit-1)', which we call 'res'; it will +** have the 'p' bit cleared. If the key is outside the array, that is, +** 'key-1 >= 2^(p+1)', then 'res' will have some bit on higher than 'p', +** therefore it will be larger or equal to 'alimit', and the check +** will fail. If 'key-1 < 2^(p+1)', then 'res' has no bit on higher than +** 'p', and as the bit 'p' itself was cleared, 'res' will be smaller +** than 2^p, therefore smaller than 'alimit', and the check succeeds. +** As special cases, when 'alimit' is 0 the condition is trivially false, +** and when 'alimit' is 1 the condition simplifies to 'key-1 < alimit'. +** If key is 0 or negative, 'res' will have its higher bit on, so that +** if cannot be smaller than alimit. */ const TValue *luaH_getint (Table *t, lua_Integer key) { - /* (1 <= key && key <= t->sizearray) */ - if (l_castS2U(key - 1) < t->sizearray) + lua_Unsigned alimit = t->alimit; + if (l_castS2U(key) - 1u < alimit) /* 'key' in [1, t->alimit]? */ return &t->array[key - 1]; - else { + else if (!isrealasize(t) && /* key still may be in the array part? */ + (((l_castS2U(key) - 1u) & ~(alimit - 1u)) < alimit)) { + t->alimit = cast_uint(key); /* probably '#t' is here now */ + return &t->array[key - 1]; + } + else { /* key is not in the array part; check the hash */ Node *n = hashint(t, key); for (;;) { /* check whether 'key' is somewhere in the chain */ - if (ttisinteger(gkey(n)) && ivalue(gkey(n)) == key) + if (keyisinteger(n) && keyival(n) == key) return gval(n); /* that's it */ else { int nx = gnext(n); if (nx == 0) break; n += nx; } - }; - return luaO_nilobject; + } + return &absentkey; } } @@ -522,20 +770,30 @@ const TValue *luaH_getint (Table *t, lua_Integer key) { /* ** search function for short strings */ -const TValue *luaH_getstr (Table *t, TString *key) { +const TValue *luaH_getshortstr (Table *t, TString *key) { Node *n = hashstr(t, key); - lua_assert(key->tt == LUA_TSHRSTR); + lua_assert(key->tt == LUA_VSHRSTR); for (;;) { /* check whether 'key' is somewhere in the chain */ - const TValue *k = gkey(n); - if (ttisshrstring(k) && eqshrstr(tsvalue(k), key)) + if (keyisshrstr(n) && eqshrstr(keystrval(n), key)) return gval(n); /* that's it */ else { int nx = gnext(n); - if (nx == 0) break; + if (nx == 0) + return &absentkey; /* not found */ n += nx; } - }; - return luaO_nilobject; + } +} + + +const TValue *luaH_getstr (Table *t, TString *key) { + if (key->tt == LUA_VSHRSTR) + return luaH_getshortstr(t, key); + else { /* for long strings, use generic case */ + TValue ko; + setsvalue(cast(lua_State *, NULL), &ko, key); + return getgeneric(t, &ko, 0); + } } @@ -543,77 +801,102 @@ const TValue *luaH_getstr (Table *t, TString *key) { ** main search function */ const TValue *luaH_get (Table *t, const TValue *key) { - switch (ttype(key)) { - case LUA_TSHRSTR: return luaH_getstr(t, tsvalue(key)); - case LUA_TNUMINT: return luaH_getint(t, ivalue(key)); - case LUA_TNIL: return luaO_nilobject; - case LUA_TNUMFLT: { + switch (ttypetag(key)) { + case LUA_VSHRSTR: return luaH_getshortstr(t, tsvalue(key)); + case LUA_VNUMINT: return luaH_getint(t, ivalue(key)); + case LUA_VNIL: return &absentkey; + case LUA_VNUMFLT: { lua_Integer k; - if (luaV_tointeger(key, &k, 0)) /* index is int? */ + if (luaV_flttointeger(fltvalue(key), &k, F2Ieq)) /* integral index? */ return luaH_getint(t, k); /* use specialized version */ /* else... */ } /* FALLTHROUGH */ - default: { - Node *n = mainposition(t, key); - for (;;) { /* check whether 'key' is somewhere in the chain */ - if (luaV_rawequalobj(gkey(n), key)) - return gval(n); /* that's it */ - else { - int nx = gnext(n); - if (nx == 0) break; - n += nx; - } - }; - return luaO_nilobject; - } + default: + return getgeneric(t, key, 0); } } +/* +** Finish a raw "set table" operation, where 'slot' is where the value +** should have been (the result of a previous "get table"). +** Beware: when using this function you probably need to check a GC +** barrier and invalidate the TM cache. +*/ +void luaH_finishset (lua_State *L, Table *t, const TValue *key, + const TValue *slot, TValue *value) { + if (isabstkey(slot)) + luaH_newkey(L, t, key, value); + else + setobj2t(L, cast(TValue *, slot), value); +} + + /* ** beware: when using this function you probably need to check a GC ** barrier and invalidate the TM cache. */ -TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { - const TValue *p = luaH_get(t, key); - if (p != luaO_nilobject) - return cast(TValue *, p); - else return luaH_newkey(L, t, key); +void luaH_set (lua_State *L, Table *t, const TValue *key, TValue *value) { + const TValue *slot = luaH_get(t, key); + luaH_finishset(L, t, key, slot, value); } void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) { const TValue *p = luaH_getint(t, key); - TValue *cell; - if (p != luaO_nilobject) - cell = cast(TValue *, p); - else { + if (isabstkey(p)) { TValue k; setivalue(&k, key); - cell = luaH_newkey(L, t, &k); + luaH_newkey(L, t, &k, value); } - setobj2t(L, cell, value); + else + setobj2t(L, cast(TValue *, p), value); } -static int unbound_search (Table *t, unsigned int j) { - unsigned int i = j; /* i is zero or a present index */ - j++; - /* find 'i' and 'j' such that i is present and j is not */ - while (!ttisnil(luaH_getint(t, j))) { - i = j; - if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */ - /* table was built with bad purposes: resort to linear search */ - i = 1; - while (!ttisnil(luaH_getint(t, i))) i++; - return i - 1; +/* +** Try to find a boundary in the hash part of table 't'. From the +** caller, we know that 'j' is zero or present and that 'j + 1' is +** present. We want to find a larger key that is absent from the +** table, so that we can do a binary search between the two keys to +** find a boundary. We keep doubling 'j' until we get an absent index. +** If the doubling would overflow, we try LUA_MAXINTEGER. If it is +** absent, we are ready for the binary search. ('j', being max integer, +** is larger or equal to 'i', but it cannot be equal because it is +** absent while 'i' is present; so 'j > i'.) Otherwise, 'j' is a +** boundary. ('j + 1' cannot be a present integer key because it is +** not a valid integer in Lua.) +*/ +static lua_Unsigned hash_search (Table *t, lua_Unsigned j) { + lua_Unsigned i; + if (j == 0) j++; /* the caller ensures 'j + 1' is present */ + do { + i = j; /* 'i' is a present index */ + if (j <= l_castS2U(LUA_MAXINTEGER) / 2) + j *= 2; + else { + j = LUA_MAXINTEGER; + if (isempty(luaH_getint(t, j))) /* t[j] not present? */ + break; /* 'j' now is an absent index */ + else /* weird case */ + return j; /* well, max integer is a boundary... */ } - j *= 2; + } while (!isempty(luaH_getint(t, j))); /* repeat until an absent t[j] */ + /* i < j && t[i] present && t[j] absent */ + while (j - i > 1u) { /* do a binary search between them */ + lua_Unsigned m = (i + j) / 2; + if (isempty(luaH_getint(t, m))) j = m; + else i = m; } - /* now do a binary search between them */ - while (j - i > 1) { - unsigned int m = (i+j)/2; - if (ttisnil(luaH_getint(t, m))) j = m; + return i; +} + + +static unsigned int binsearch (const TValue *array, unsigned int i, + unsigned int j) { + while (j - i > 1u) { /* binary search */ + unsigned int m = (i + j) / 2; + if (isempty(&array[m - 1])) j = m; else i = m; } return i; @@ -621,35 +904,92 @@ static int unbound_search (Table *t, unsigned int j) { /* -** Try to find a boundary in table 't'. A 'boundary' is an integer index -** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). +** Try to find a boundary in table 't'. (A 'boundary' is an integer index +** such that t[i] is present and t[i+1] is absent, or 0 if t[1] is absent +** and 'maxinteger' if t[maxinteger] is present.) +** (In the next explanation, we use Lua indices, that is, with base 1. +** The code itself uses base 0 when indexing the array part of the table.) +** The code starts with 'limit = t->alimit', a position in the array +** part that may be a boundary. +** +** (1) If 't[limit]' is empty, there must be a boundary before it. +** As a common case (e.g., after 't[#t]=nil'), check whether 'limit-1' +** is present. If so, it is a boundary. Otherwise, do a binary search +** between 0 and limit to find a boundary. In both cases, try to +** use this boundary as the new 'alimit', as a hint for the next call. +** +** (2) If 't[limit]' is not empty and the array has more elements +** after 'limit', try to find a boundary there. Again, try first +** the special case (which should be quite frequent) where 'limit+1' +** is empty, so that 'limit' is a boundary. Otherwise, check the +** last element of the array part. If it is empty, there must be a +** boundary between the old limit (present) and the last element +** (absent), which is found with a binary search. (This boundary always +** can be a new limit.) +** +** (3) The last case is when there are no elements in the array part +** (limit == 0) or its last element (the new limit) is present. +** In this case, must check the hash part. If there is no hash part +** or 'limit+1' is absent, 'limit' is a boundary. Otherwise, call +** 'hash_search' to find a boundary in the hash part of the table. +** (In those cases, the boundary is not inside the array part, and +** therefore cannot be used as a new limit.) */ -int luaH_getn (Table *t) { - unsigned int j = t->sizearray; - if (j > 0 && ttisnil(&t->array[j - 1])) { - /* there is a boundary in the array part: (binary) search for it */ - unsigned int i = 0; - while (j - i > 1) { - unsigned int m = (i+j)/2; - if (ttisnil(&t->array[m - 1])) j = m; - else i = m; +lua_Unsigned luaH_getn (Table *t) { + unsigned int limit = t->alimit; + if (limit > 0 && isempty(&t->array[limit - 1])) { /* (1)? */ + /* there must be a boundary before 'limit' */ + if (limit >= 2 && !isempty(&t->array[limit - 2])) { + /* 'limit - 1' is a boundary; can it be a new limit? */ + if (ispow2realasize(t) && !ispow2(limit - 1)) { + t->alimit = limit - 1; + setnorealasize(t); /* now 'alimit' is not the real size */ + } + return limit - 1; + } + else { /* must search for a boundary in [0, limit] */ + unsigned int boundary = binsearch(t->array, 0, limit); + /* can this boundary represent the real size of the array? */ + if (ispow2realasize(t) && boundary > luaH_realasize(t) / 2) { + t->alimit = boundary; /* use it as the new limit */ + setnorealasize(t); + } + return boundary; } - return i; } - /* else must find a boundary in hash part */ - else if (isdummy(t->node)) /* hash part is empty? */ - return j; /* that is easy... */ - else return unbound_search(t, j); + /* 'limit' is zero or present in table */ + if (!limitequalsasize(t)) { /* (2)? */ + /* 'limit' > 0 and array has more elements after 'limit' */ + if (isempty(&t->array[limit])) /* 'limit + 1' is empty? */ + return limit; /* this is the boundary */ + /* else, try last element in the array */ + limit = luaH_realasize(t); + if (isempty(&t->array[limit - 1])) { /* empty? */ + /* there must be a boundary in the array after old limit, + and it must be a valid new limit */ + unsigned int boundary = binsearch(t->array, t->alimit, limit); + t->alimit = boundary; + return boundary; + } + /* else, new limit is present in the table; check the hash part */ + } + /* (3) 'limit' is the last element and either is zero or present in table */ + lua_assert(limit == luaH_realasize(t) && + (limit == 0 || !isempty(&t->array[limit - 1]))); + if (isdummy(t) || isempty(luaH_getint(t, cast(lua_Integer, limit + 1)))) + return limit; /* 'limit + 1' is absent */ + else /* 'limit + 1' is also present */ + return hash_search(t, limit); } #if defined(LUA_DEBUG) +/* export these functions for the test library */ + Node *luaH_mainposition (const Table *t, const TValue *key) { - return mainposition(t, key); + return mainpositionTV(t, key); } -int luaH_isdummy (Node *n) { return isdummy(n); } - #endif diff --git a/libs/lua/ltable.h b/libs/lua/ltable.h index 53d25511..8e689034 100644 --- a/libs/lua/ltable.h +++ b/libs/lua/ltable.h @@ -1,5 +1,5 @@ /* -** $Id: ltable.h,v 2.20 2014/09/04 18:15:29 roberto Exp $ +** $Id: ltable.h $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ @@ -12,41 +12,51 @@ #define gnode(t,i) (&(t)->node[i]) #define gval(n) (&(n)->i_val) -#define gnext(n) ((n)->i_key.nk.next) +#define gnext(n) ((n)->u.next) -/* 'const' to avoid wrong writings that can mess up field 'next' */ -#define gkey(n) cast(const TValue*, (&(n)->i_key.tvk)) +/* +** Clear all bits of fast-access metamethods, which means that the table +** may have any of these metamethods. (First access that fails after the +** clearing will set the bit again.) +*/ +#define invalidateTMcache(t) ((t)->flags &= ~maskflags) + + +/* true when 't' is using 'dummynode' as its hash part */ +#define isdummy(t) ((t)->lastfree == NULL) -#define wgkey(n) (&(n)->i_key.nk) -#define invalidateTMcache(t) ((t)->flags = 0) +/* allocated size for hash nodes */ +#define allocsizenode(t) (isdummy(t) ? 0 : sizenode(t)) -/* returns the key, given the value of a table entry */ -#define keyfromval(v) \ - (gkey(cast(Node *, cast(char *, (v)) - offsetof(Node, i_val)))) +/* returns the Node, given the value of a table entry */ +#define nodefromval(v) cast(Node *, (v)) LUAI_FUNC const TValue *luaH_getint (Table *t, lua_Integer key); LUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value); +LUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); -LUAI_FUNC TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key); -LUAI_FUNC TValue *luaH_set (lua_State *L, Table *t, const TValue *key); +LUAI_FUNC void luaH_set (lua_State *L, Table *t, const TValue *key, + TValue *value); +LUAI_FUNC void luaH_finishset (lua_State *L, Table *t, const TValue *key, + const TValue *slot, TValue *value); LUAI_FUNC Table *luaH_new (lua_State *L); LUAI_FUNC void luaH_resize (lua_State *L, Table *t, unsigned int nasize, unsigned int nhsize); LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize); LUAI_FUNC void luaH_free (lua_State *L, Table *t); LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); -LUAI_FUNC int luaH_getn (Table *t); +LUAI_FUNC lua_Unsigned luaH_getn (Table *t); +LUAI_FUNC unsigned int luaH_realasize (const Table *t); #if defined(LUA_DEBUG) LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); -LUAI_FUNC int luaH_isdummy (Node *n); #endif diff --git a/libs/lua/ltablib.c b/libs/lua/ltablib.c index a05c885c..e6bc4d04 100644 --- a/libs/lua/ltablib.c +++ b/libs/lua/ltablib.c @@ -1,5 +1,5 @@ /* -** $Id: ltablib.c,v 1.80 2015/01/13 16:27:29 roberto Exp $ +** $Id: ltablib.c $ ** Library for Table Manipulation ** See Copyright Notice in lua.h */ @@ -12,6 +12,7 @@ #include #include +#include #include "lua.h" @@ -19,64 +20,48 @@ #include "lualib.h" - /* -** Structure with table-access functions +** Operations that an object must define to mimic a table +** (some functions only need some of them) */ -typedef struct { - int (*geti) (lua_State *L, int idx, lua_Integer n); - void (*seti) (lua_State *L, int idx, lua_Integer n); -} TabA; +#define TAB_R 1 /* read */ +#define TAB_W 2 /* write */ +#define TAB_L 4 /* length */ +#define TAB_RW (TAB_R | TAB_W) /* read/write */ -/* -** Check that 'arg' has a table and set access functions in 'ta' to raw -** or non-raw according to the presence of corresponding metamethods. -*/ -static void checktab (lua_State *L, int arg, TabA *ta) { - ta->geti = NULL; ta->seti = NULL; - if (lua_getmetatable(L, arg)) { - lua_pushliteral(L, "__index"); /* 'index' metamethod */ - if (lua_rawget(L, -2) != LUA_TNIL) - ta->geti = lua_geti; - lua_pushliteral(L, "__newindex"); /* 'newindex' metamethod */ - if (lua_rawget(L, -3) != LUA_TNIL) - ta->seti = lua_seti; - lua_pop(L, 3); /* pop metatable plus both metamethods */ - } - if (ta->geti == NULL || ta->seti == NULL) { - luaL_checktype(L, arg, LUA_TTABLE); /* must be table for raw methods */ - if (ta->geti == NULL) ta->geti = lua_rawgeti; - if (ta->seti == NULL) ta->seti = lua_rawseti; - } -} +#define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n)) -#define aux_getn(L,n,ta) (checktab(L, n, ta), luaL_len(L, n)) +static int checkfield (lua_State *L, const char *key, int n) { + lua_pushstring(L, key); + return (lua_rawget(L, -n) != LUA_TNIL); +} -#if defined(LUA_COMPAT_MAXN) -static int maxn (lua_State *L) { - lua_Number max = 0; - luaL_checktype(L, 1, LUA_TTABLE); - lua_pushnil(L); /* first key */ - while (lua_next(L, 1)) { - lua_pop(L, 1); /* remove value */ - if (lua_type(L, -1) == LUA_TNUMBER) { - lua_Number v = lua_tonumber(L, -1); - if (v > max) max = v; +/* +** Check that 'arg' either is a table or can behave like one (that is, +** has a metatable with the required metamethods) +*/ +static void checktab (lua_State *L, int arg, int what) { + if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ + int n = 1; /* number of elements to pop */ + if (lua_getmetatable(L, arg) && /* must have metatable */ + (!(what & TAB_R) || checkfield(L, "__index", ++n)) && + (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && + (!(what & TAB_L) || checkfield(L, "__len", ++n))) { + lua_pop(L, n); /* pop metatable and tested metamethods */ } + else + luaL_checktype(L, arg, LUA_TTABLE); /* force an error */ } - lua_pushnumber(L, max); - return 1; } -#endif static int tinsert (lua_State *L) { - TabA ta; - lua_Integer e = aux_getn(L, 1, &ta) + 1; /* first empty element */ lua_Integer pos; /* where to insert new element */ + lua_Integer e = aux_getn(L, 1, TAB_RW); + e = luaL_intop(+, e, 1); /* first empty element */ switch (lua_gettop(L)) { case 2: { /* called with only 2 arguments */ pos = e; /* insert new element at the end */ @@ -85,10 +70,12 @@ static int tinsert (lua_State *L) { case 3: { lua_Integer i; pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ - luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds"); + /* check whether 'pos' is in [1, e] */ + luaL_argcheck(L, (lua_Unsigned)pos - 1u < (lua_Unsigned)e, 2, + "position out of bounds"); for (i = e; i > pos; i--) { /* move up elements */ - (*ta.geti)(L, 1, i - 1); - (*ta.seti)(L, 1, i); /* t[i] = t[i - 1] */ + lua_geti(L, 1, i - 1); + lua_seti(L, 1, i); /* t[i] = t[i - 1] */ } break; } @@ -96,90 +83,90 @@ static int tinsert (lua_State *L) { return luaL_error(L, "wrong number of arguments to 'insert'"); } } - (*ta.seti)(L, 1, pos); /* t[pos] = v */ + lua_seti(L, 1, pos); /* t[pos] = v */ return 0; } static int tremove (lua_State *L) { - TabA ta; - lua_Integer size = aux_getn(L, 1, &ta); + lua_Integer size = aux_getn(L, 1, TAB_RW); lua_Integer pos = luaL_optinteger(L, 2, size); if (pos != size) /* validate 'pos' if given */ - luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds"); - (*ta.geti)(L, 1, pos); /* result = t[pos] */ + /* check whether 'pos' is in [1, size + 1] */ + luaL_argcheck(L, (lua_Unsigned)pos - 1u <= (lua_Unsigned)size, 2, + "position out of bounds"); + lua_geti(L, 1, pos); /* result = t[pos] */ for ( ; pos < size; pos++) { - (*ta.geti)(L, 1, pos + 1); - (*ta.seti)(L, 1, pos); /* t[pos] = t[pos + 1] */ + lua_geti(L, 1, pos + 1); + lua_seti(L, 1, pos); /* t[pos] = t[pos + 1] */ } lua_pushnil(L); - (*ta.seti)(L, 1, pos); /* t[pos] = nil */ + lua_seti(L, 1, pos); /* remove entry t[pos] */ return 1; } +/* +** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever +** possible, copy in increasing order, which is better for rehashing. +** "possible" means destination after original range, or smaller +** than origin, or copying to another table. +*/ static int tmove (lua_State *L) { - TabA ta; lua_Integer f = luaL_checkinteger(L, 2); lua_Integer e = luaL_checkinteger(L, 3); lua_Integer t = luaL_checkinteger(L, 4); int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ + checktab(L, 1, TAB_R); + checktab(L, tt, TAB_W); if (e >= f) { /* otherwise, nothing to move */ lua_Integer n, i; - ta.geti = (luaL_getmetafield(L, 1, "__index") == LUA_TNIL) - ? (luaL_checktype(L, 1, LUA_TTABLE), lua_rawgeti) - : lua_geti; - ta.seti = (luaL_getmetafield(L, tt, "__newindex") == LUA_TNIL) - ? (luaL_checktype(L, tt, LUA_TTABLE), lua_rawseti) - : lua_seti; luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3, "too many elements to move"); n = e - f + 1; /* number of elements to move */ luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4, "destination wrap around"); - if (t > f) { - for (i = n - 1; i >= 0; i--) { - (*ta.geti)(L, 1, f + i); - (*ta.seti)(L, tt, t + i); + if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) { + for (i = 0; i < n; i++) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); } } else { - for (i = 0; i < n; i++) { - (*ta.geti)(L, 1, f + i); - (*ta.seti)(L, tt, t + i); + for (i = n - 1; i >= 0; i--) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); } } } - lua_pushvalue(L, tt); /* return "to table" */ + lua_pushvalue(L, tt); /* return destination table */ return 1; } -static void addfield (lua_State *L, luaL_Buffer *b, TabA *ta, lua_Integer i) { - (*ta->geti)(L, 1, i); - if (!lua_isstring(L, -1)) - luaL_error(L, "invalid value (%s) at index %d in table for 'concat'", - luaL_typename(L, -1), i); +static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) { + lua_geti(L, 1, i); + if (l_unlikely(!lua_isstring(L, -1))) + luaL_error(L, "invalid value (%s) at index %I in table for 'concat'", + luaL_typename(L, -1), (LUAI_UACINT)i); luaL_addvalue(b); } static int tconcat (lua_State *L) { - TabA ta; luaL_Buffer b; + lua_Integer last = aux_getn(L, 1, TAB_R); size_t lsep; - lua_Integer i, last; const char *sep = luaL_optlstring(L, 2, "", &lsep); - checktab(L, 1, &ta); - i = luaL_optinteger(L, 3, 1); - last = luaL_opt(L, luaL_checkinteger, 4, luaL_len(L, 1)); + lua_Integer i = luaL_optinteger(L, 3, 1); + last = luaL_optinteger(L, 4, last); luaL_buffinit(L, &b); for (; i < last; i++) { - addfield(L, &b, &ta, i); + addfield(L, &b, i); luaL_addlstring(&b, sep, lsep); } if (i == last) /* add last value (if interval was not empty) */ - addfield(L, &b, &ta, i); + addfield(L, &b, i); luaL_pushresult(&b); return 1; } @@ -191,34 +178,32 @@ static int tconcat (lua_State *L) { ** ======================================================= */ -static int pack (lua_State *L) { +static int tpack (lua_State *L) { int i; int n = lua_gettop(L); /* number of elements to pack */ lua_createtable(L, n, 1); /* create result table */ lua_insert(L, 1); /* put it at index 1 */ for (i = n; i >= 1; i--) /* assign elements */ - lua_rawseti(L, 1, i); + lua_seti(L, 1, i); lua_pushinteger(L, n); lua_setfield(L, 1, "n"); /* t.n = number of elements */ return 1; /* return table */ } -static int unpack (lua_State *L) { - TabA ta; - lua_Integer i, e; +static int tunpack (lua_State *L) { lua_Unsigned n; - checktab(L, 1, &ta); - i = luaL_optinteger(L, 2, 1); - e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); + lua_Integer i = luaL_optinteger(L, 2, 1); + lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); if (i > e) return 0; /* empty range */ n = (lua_Unsigned)e - i; /* number of elements minus 1 (avoid overflows) */ - if (n >= (unsigned int)INT_MAX || !lua_checkstack(L, (int)(++n))) + if (l_unlikely(n >= (unsigned int)INT_MAX || + !lua_checkstack(L, (int)(++n)))) return luaL_error(L, "too many results to unpack"); - do { /* must have at least one element */ - (*ta.geti)(L, 1, i); /* push arg[i..e] */ - } while (i++ < e); - + for (; i < e; i++) { /* push arg[i..e - 1] (to avoid overflows) */ + lua_geti(L, 1, i); + } + lua_geti(L, 1, e); /* push last element */ return (int)n; } @@ -235,97 +220,191 @@ static int unpack (lua_State *L) { */ -static void set2 (lua_State *L, TabA *ta, int i, int j) { - (*ta->seti)(L, 1, i); - (*ta->seti)(L, 1, j); +/* type for array indices */ +typedef unsigned int IdxT; + + +/* +** Produce a "random" 'unsigned int' to randomize pivot choice. This +** macro is used only when 'sort' detects a big imbalance in the result +** of a partition. (If you don't want/need this "randomness", ~0 is a +** good choice.) +*/ +#if !defined(l_randomizePivot) /* { */ + +#include + +/* size of 'e' measured in number of 'unsigned int's */ +#define sof(e) (sizeof(e) / sizeof(unsigned int)) + +/* +** Use 'time' and 'clock' as sources of "randomness". Because we don't +** know the types 'clock_t' and 'time_t', we cannot cast them to +** anything without risking overflows. A safe way to use their values +** is to copy them to an array of a known type and use the array values. +*/ +static unsigned int l_randomizePivot (void) { + clock_t c = clock(); + time_t t = time(NULL); + unsigned int buff[sof(c) + sof(t)]; + unsigned int i, rnd = 0; + memcpy(buff, &c, sof(c) * sizeof(unsigned int)); + memcpy(buff + sof(c), &t, sof(t) * sizeof(unsigned int)); + for (i = 0; i < sof(buff); i++) + rnd += buff[i]; + return rnd; +} + +#endif /* } */ + + +/* arrays larger than 'RANLIMIT' may use randomized pivots */ +#define RANLIMIT 100u + + +static void set2 (lua_State *L, IdxT i, IdxT j) { + lua_seti(L, 1, i); + lua_seti(L, 1, j); } + +/* +** Return true iff value at stack index 'a' is less than the value at +** index 'b' (according to the order of the sort). +*/ static int sort_comp (lua_State *L, int a, int b) { - if (!lua_isnil(L, 2)) { /* function? */ + if (lua_isnil(L, 2)) /* no function? */ + return lua_compare(L, a, b, LUA_OPLT); /* a < b */ + else { /* function */ int res; - lua_pushvalue(L, 2); + lua_pushvalue(L, 2); /* push function */ lua_pushvalue(L, a-1); /* -1 to compensate function */ lua_pushvalue(L, b-2); /* -2 to compensate function and 'a' */ - lua_call(L, 2, 1); - res = lua_toboolean(L, -1); - lua_pop(L, 1); + lua_call(L, 2, 1); /* call function */ + res = lua_toboolean(L, -1); /* get result */ + lua_pop(L, 1); /* pop result */ return res; } - else /* a < b? */ - return lua_compare(L, a, b, LUA_OPLT); } -static void auxsort (lua_State *L, TabA *ta, int l, int u) { - while (l < u) { /* for tail recursion */ - int i, j; - /* sort elements a[l], a[(l+u)/2] and a[u] */ - (*ta->geti)(L, 1, l); - (*ta->geti)(L, 1, u); - if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */ - set2(L, ta, l, u); /* swap a[l] - a[u] */ + +/* +** Does the partition: Pivot P is at the top of the stack. +** precondition: a[lo] <= P == a[up-1] <= a[up], +** so it only needs to do the partition from lo + 1 to up - 2. +** Pos-condition: a[lo .. i - 1] <= a[i] == P <= a[i + 1 .. up] +** returns 'i'. +*/ +static IdxT partition (lua_State *L, IdxT lo, IdxT up) { + IdxT i = lo; /* will be incremented before first use */ + IdxT j = up - 1; /* will be decremented before first use */ + /* loop invariant: a[lo .. i] <= P <= a[j .. up] */ + for (;;) { + /* next loop: repeat ++i while a[i] < P */ + while ((void)lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (l_unlikely(i == up - 1)) /* a[i] < P but a[up - 1] == P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* after the loop, a[i] >= P and a[lo .. i - 1] < P */ + /* next loop: repeat --j while P < a[j] */ + while ((void)lua_geti(L, 1, --j), sort_comp(L, -3, -1)) { + if (l_unlikely(j < i)) /* j < i but a[j] > P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + /* after the loop, a[j] <= P and a[j + 1 .. up] >= P */ + if (j < i) { /* no elements out of place? */ + /* a[lo .. i - 1] <= P <= a[j + 1 .. i .. up] */ + lua_pop(L, 1); /* pop a[j] */ + /* swap pivot (a[up - 1]) with a[i] to satisfy pos-condition */ + set2(L, up - 1, i); + return i; + } + /* otherwise, swap a[i] - a[j] to restore invariant and repeat */ + set2(L, i, j); + } +} + + +/* +** Choose an element in the middle (2nd-3th quarters) of [lo,up] +** "randomized" by 'rnd' +*/ +static IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) { + IdxT r4 = (up - lo) / 4; /* range/4 */ + IdxT p = rnd % (r4 * 2) + (lo + r4); + lua_assert(lo + r4 <= p && p <= up - r4); + return p; +} + + +/* +** Quicksort algorithm (recursive function) +*/ +static void auxsort (lua_State *L, IdxT lo, IdxT up, + unsigned int rnd) { + while (lo < up) { /* loop for tail recursion */ + IdxT p; /* Pivot index */ + IdxT n; /* to be used later */ + /* sort elements 'lo', 'p', and 'up' */ + lua_geti(L, 1, lo); + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[lo]? */ + set2(L, lo, up); /* swap a[lo] - a[up] */ else - lua_pop(L, 2); - if (u-l == 1) break; /* only 2 elements */ - i = (l+u)/2; - (*ta->geti)(L, 1, i); - (*ta->geti)(L, 1, l); - if (sort_comp(L, -2, -1)) /* a[i]geti)(L, 1, u); - if (sort_comp(L, -1, -2)) /* a[u]geti)(L, 1, i); /* Pivot */ - lua_pushvalue(L, -1); - (*ta->geti)(L, 1, u-1); - set2(L, ta, i, u-1); - /* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */ - i = l; j = u-1; - for (;;) { /* invariant: a[l..i] <= P <= a[j..u] */ - /* repeat ++i until a[i] >= P */ - while ((*ta->geti)(L, 1, ++i), sort_comp(L, -1, -2)) { - if (i>=u) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[i] */ - } - /* repeat --j until a[j] <= P */ - while ((*ta->geti)(L, 1, --j), sort_comp(L, -3, -1)) { - if (j<=l) luaL_error(L, "invalid order function for sorting"); - lua_pop(L, 1); /* remove a[j] */ - } - if (jgeti)(L, 1, u-1); - (*ta->geti)(L, 1, i); - set2(L, ta, u-1, i); /* swap pivot (a[u-1]) with a[i] */ - /* a[l..i-1] <= a[i] == P <= a[i+1..u] */ - /* adjust so that smaller half is in [j..i] and larger one in [l..u] */ - if (i-l < u-i) { - j=l; i=i-1; l=i+2; + if (up - lo == 2) /* only 3 elements? */ + return; /* already sorted */ + lua_geti(L, 1, p); /* get middle element (Pivot) */ + lua_pushvalue(L, -1); /* push Pivot */ + lua_geti(L, 1, up - 1); /* push a[up - 1] */ + set2(L, p, up - 1); /* swap Pivot (a[p]) with a[up - 1] */ + p = partition(L, lo, up); + /* a[lo .. p - 1] <= a[p] == P <= a[p + 1 .. up] */ + if (p - lo < up - p) { /* lower interval is smaller? */ + auxsort(L, lo, p - 1, rnd); /* call recursively for lower interval */ + n = p - lo; /* size of smaller interval */ + lo = p + 1; /* tail call for [p + 1 .. up] (upper interval) */ } else { - j=i+1; i=u; u=j-2; + auxsort(L, p + 1, up, rnd); /* call recursively for upper interval */ + n = up - p; /* size of smaller interval */ + up = p - 1; /* tail call for [lo .. p - 1] (lower interval) */ } - auxsort(L, ta, j, i); /* call recursively the smaller one */ - } /* repeat the routine for the larger one */ + if ((up - lo) / 128 > n) /* partition too imbalanced? */ + rnd = l_randomizePivot(); /* try a new randomization */ + } /* tail call auxsort(L, lo, up, rnd) */ } + static int sort (lua_State *L) { - TabA ta; - int n = (int)aux_getn(L, 1, &ta); - luaL_checkstack(L, 50, ""); /* assume array is smaller than 2^50 */ - if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ - luaL_checktype(L, 2, LUA_TFUNCTION); - lua_settop(L, 2); /* make sure there are two arguments */ - auxsort(L, &ta, 1, n); + lua_Integer n = aux_getn(L, 1, TAB_RW); + if (n > 1) { /* non-trivial interval? */ + luaL_argcheck(L, n < INT_MAX, 1, "array too big"); + if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checktype(L, 2, LUA_TFUNCTION); /* must be a function */ + lua_settop(L, 2); /* make sure there are two arguments */ + auxsort(L, 1, (IdxT)n, 0); + } return 0; } @@ -334,12 +413,9 @@ static int sort (lua_State *L) { static const luaL_Reg tab_funcs[] = { {"concat", tconcat}, -#if defined(LUA_COMPAT_MAXN) - {"maxn", maxn}, -#endif {"insert", tinsert}, - {"pack", pack}, - {"unpack", unpack}, + {"pack", tpack}, + {"unpack", tunpack}, {"remove", tremove}, {"move", tmove}, {"sort", sort}, @@ -349,11 +425,6 @@ static const luaL_Reg tab_funcs[] = { LUAMOD_API int luaopen_table (lua_State *L) { luaL_newlib(L, tab_funcs); -#if defined(LUA_COMPAT_UNPACK) - /* _G.unpack = table.unpack */ - lua_getfield(L, -1, "unpack"); - lua_setglobal(L, "unpack"); -#endif return 1; } diff --git a/libs/lua/ltm.c b/libs/lua/ltm.c index c38e5c34..07a06081 100644 --- a/libs/lua/ltm.c +++ b/libs/lua/ltm.c @@ -1,5 +1,5 @@ /* -** $Id: ltm.c,v 2.34 2015/03/30 15:42:27 roberto Exp $ +** $Id: ltm.c $ ** Tag methods ** See Copyright Notice in lua.h */ @@ -15,7 +15,8 @@ #include "lua.h" #include "ldebug.h" -#include "ldo.h" +#include "ldo.h" +#include "lgc.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" @@ -26,11 +27,11 @@ static const char udatatypename[] = "userdata"; -LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTAGS] = { +LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTYPES] = { "no value", "nil", "boolean", udatatypename, "number", "string", "table", "function", udatatypename, "thread", - "proto" /* this last case is used for tests only */ + "upvalue", "proto" /* these last cases are used for tests only */ }; @@ -42,7 +43,7 @@ void luaT_init (lua_State *L) { "__div", "__idiv", "__band", "__bor", "__bxor", "__shl", "__shr", "__unm", "__bnot", "__lt", "__le", - "__concat", "__call" + "__concat", "__call", "__close" }; int i; for (i=0; iflags |= cast_byte(1u<metatable; break; @@ -77,51 +78,80 @@ const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) { mt = uvalue(o)->metatable; break; default: - mt = G(L)->mt[ttnov(o)]; + mt = G(L)->mt[ttype(o)]; } - return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); + return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : &G(L)->nilvalue); +} + + +/* +** Return the name of the type of an object. For tables and userdata +** with metatable, use their '__name' metafield, if present. +*/ +const char *luaT_objtypename (lua_State *L, const TValue *o) { + Table *mt; + if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) || + (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) { + const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name")); + if (ttisstring(name)) /* is '__name' a string? */ + return getstr(tsvalue(name)); /* use it as type name */ + } + return ttypename(ttype(o)); /* else use standard type name */ } void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, - const TValue *p2, TValue *p3, int hasres) { - ptrdiff_t result = savestack(L, p3); - setobj2s(L, L->top++, f); /* push function (assume EXTRA_STACK) */ - setobj2s(L, L->top++, p1); /* 1st argument */ - setobj2s(L, L->top++, p2); /* 2nd argument */ - if (!hasres) /* no result? 'p3' is third argument */ - setobj2s(L, L->top++, p3); /* 3rd argument */ + const TValue *p2, const TValue *p3) { + StkId func = L->top.p; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + setobj2s(L, func + 3, p3); /* 3rd argument */ + L->top.p = func + 4; /* metamethod may yield only when called from Lua code */ - luaD_call(L, L->top - (4 - hasres), hasres, isLua(L->ci)); - if (hasres) { /* if has result, move it to its place */ - p3 = restorestack(L, result); - setobjs2s(L, p3, --L->top); - } + if (isLuacode(L->ci)) + luaD_call(L, func, 0); + else + luaD_callnoyield(L, func, 0); } -int luaT_callbinTM (lua_State *L, const TValue *p1, const TValue *p2, - StkId res, TMS event) { +void luaT_callTMres (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, StkId res) { + ptrdiff_t result = savestack(L, res); + StkId func = L->top.p; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + L->top.p += 3; + /* metamethod may yield only when called from Lua code */ + if (isLuacode(L->ci)) + luaD_call(L, func, 1); + else + luaD_callnoyield(L, func, 1); + res = restorestack(L, result); + setobjs2s(L, res, --L->top.p); /* move result to its place */ +} + + +static int callbinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ - if (ttisnil(tm)) + if (notm(tm)) tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ - if (ttisnil(tm)) return 0; - luaT_callTM(L, tm, p1, p2, res, 1); + if (notm(tm)) return 0; + luaT_callTMres(L, tm, p1, p2, res); return 1; } void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, StkId res, TMS event) { - if (!luaT_callbinTM(L, p1, p2, res, event)) { + if (l_unlikely(!callbinTM(L, p1, p2, res, event))) { switch (event) { - case TM_CONCAT: - luaG_concaterror(L, p1, p2); - /* call never returns, but to avoid warnings: *//* FALLTHROUGH */ case TM_BAND: case TM_BOR: case TM_BXOR: case TM_SHL: case TM_SHR: case TM_BNOT: { - lua_Number dummy; - if (tonumber(p1, &dummy) && tonumber(p2, &dummy)) + if (ttisnumber(p1) && ttisnumber(p2)) luaG_tointerror(L, p1, p2); else luaG_opinterror(L, p1, p2, "perform bitwise operation on"); @@ -134,11 +164,108 @@ void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, } +void luaT_tryconcatTM (lua_State *L) { + StkId top = L->top.p; + if (l_unlikely(!callbinTM(L, s2v(top - 2), s2v(top - 1), top - 2, + TM_CONCAT))) + luaG_concaterror(L, s2v(top - 2), s2v(top - 1)); +} + + +void luaT_trybinassocTM (lua_State *L, const TValue *p1, const TValue *p2, + int flip, StkId res, TMS event) { + if (flip) + luaT_trybinTM(L, p2, p1, res, event); + else + luaT_trybinTM(L, p1, p2, res, event); +} + + +void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2, + int flip, StkId res, TMS event) { + TValue aux; + setivalue(&aux, i2); + luaT_trybinassocTM(L, p1, &aux, flip, res, event); +} + + +/* +** Calls an order tag method. +** For lessequal, LUA_COMPAT_LT_LE keeps compatibility with old +** behavior: if there is no '__le', try '__lt', based on l <= r iff +** !(r < l) (assuming a total order). If the metamethod yields during +** this substitution, the continuation has to know about it (to negate +** the result of rtop, event)) - return -1; /* no metamethod */ + if (callbinTM(L, p1, p2, L->top.p, event)) /* try original event */ + return !l_isfalse(s2v(L->top.p)); +#if defined(LUA_COMPAT_LT_LE) + else if (event == TM_LE) { + /* try '!(p2 < p1)' for '(p1 <= p2)' */ + L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ + if (callbinTM(L, p2, p1, L->top.p, TM_LT)) { + L->ci->callstatus ^= CIST_LEQ; /* clear mark */ + return l_isfalse(s2v(L->top.p)); + } + /* else error will remove this 'ci'; no need to clear mark */ + } +#endif + luaG_ordererror(L, p1, p2); /* no metamethod found */ + return 0; /* to avoid warnings */ +} + + +int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, + int flip, int isfloat, TMS event) { + TValue aux; const TValue *p2; + if (isfloat) { + setfltvalue(&aux, cast_num(v2)); + } + else + setivalue(&aux, v2); + if (flip) { /* arguments were exchanged? */ + p2 = p1; p1 = &aux; /* correct them */ + } else - return !l_isfalse(L->top); + p2 = &aux; + return luaT_callorderTM(L, p1, p2, event); +} + + +void luaT_adjustvarargs (lua_State *L, int nfixparams, CallInfo *ci, + const Proto *p) { + int i; + int actual = cast_int(L->top.p - ci->func.p) - 1; /* number of arguments */ + int nextra = actual - nfixparams; /* number of extra arguments */ + ci->u.l.nextraargs = nextra; + luaD_checkstack(L, p->maxstacksize + 1); + /* copy function to the top of the stack */ + setobjs2s(L, L->top.p++, ci->func.p); + /* move fixed parameters to the top of the stack */ + for (i = 1; i <= nfixparams; i++) { + setobjs2s(L, L->top.p++, ci->func.p + i); + setnilvalue(s2v(ci->func.p + i)); /* erase original parameter (for GC) */ + } + ci->func.p += actual + 1; + ci->top.p += actual + 1; + lua_assert(L->top.p <= ci->top.p && ci->top.p <= L->stack_last.p); +} + + +void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) { + int i; + int nextra = ci->u.l.nextraargs; + if (wanted < 0) { + wanted = nextra; /* get all extra arguments available */ + checkstackGCp(L, nextra, where); /* ensure stack space */ + L->top.p = where + nextra; /* next instruction will need top */ + } + for (i = 0; i < wanted && i < nextra; i++) + setobjs2s(L, where + i, ci->func.p - nextra + i); + for (; i < wanted; i++) /* complete required results with nil */ + setnilvalue(s2v(where + i)); } diff --git a/libs/lua/ltm.h b/libs/lua/ltm.h index 180179ce..73b833c6 100644 --- a/libs/lua/ltm.h +++ b/libs/lua/ltm.h @@ -1,5 +1,5 @@ /* -** $Id: ltm.h,v 2.21 2014/10/25 11:50:46 roberto Exp $ +** $Id: ltm.h $ ** Tag methods ** See Copyright Notice in lua.h */ @@ -40,10 +40,26 @@ typedef enum { TM_LE, TM_CONCAT, TM_CALL, + TM_CLOSE, TM_N /* number of elements in the enum */ } TMS; +/* +** Mask with 1 in all fast-access methods. A 1 in any of these bits +** in the flag of a (meta)table means the metatable does not have the +** corresponding metamethod field. (Bit 7 of the flag is used for +** 'isrealasize'.) +*/ +#define maskflags (~(~0u << (TM_EQ + 1))) + + +/* +** Test whether there is no tagmethod. +** (Because tagmethods use raw accesses, the result may be an "empty" nil.) +*/ +#define notm(tm) ttisnil(tm) + #define gfasttm(g,et,e) ((et) == NULL ? NULL : \ ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) @@ -51,25 +67,37 @@ typedef enum { #define fasttm(l,et,e) gfasttm(G(l), et, e) #define ttypename(x) luaT_typenames_[(x) + 1] -#define objtypename(x) ttypename(ttnov(x)) -LUAI_DDEC const char *const luaT_typenames_[LUA_TOTALTAGS]; +LUAI_DDEC(const char *const luaT_typenames_[LUA_TOTALTYPES];) +LUAI_FUNC const char *luaT_objtypename (lua_State *L, const TValue *o); + LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event); LUAI_FUNC void luaT_init (lua_State *L); LUAI_FUNC void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, - const TValue *p2, TValue *p3, int hasres); -LUAI_FUNC int luaT_callbinTM (lua_State *L, const TValue *p1, const TValue *p2, - StkId res, TMS event); + const TValue *p2, const TValue *p3); +LUAI_FUNC void luaT_callTMres (lua_State *L, const TValue *f, + const TValue *p1, const TValue *p2, StkId p3); LUAI_FUNC void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, StkId res, TMS event); +LUAI_FUNC void luaT_tryconcatTM (lua_State *L); +LUAI_FUNC void luaT_trybinassocTM (lua_State *L, const TValue *p1, + const TValue *p2, int inv, StkId res, TMS event); +LUAI_FUNC void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2, + int inv, StkId res, TMS event); LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1, const TValue *p2, TMS event); +LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, + int inv, int isfloat, TMS event); +LUAI_FUNC void luaT_adjustvarargs (lua_State *L, int nfixparams, + struct CallInfo *ci, const Proto *p); +LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, + StkId where, int wanted); #endif diff --git a/libs/lua/lua.h b/libs/lua/lua.h index 1c2b95ac..f050dac0 100644 --- a/libs/lua/lua.h +++ b/libs/lua/lua.h @@ -1,5 +1,5 @@ /* -** $Id: lua.h,v 1.328 2015/06/03 13:03:38 roberto Exp $ +** $Id: lua.h $ ** Lua - A Scripting Language ** Lua.org, PUC-Rio, Brazil (http://www.lua.org) ** See Copyright Notice at the end of this file @@ -17,13 +17,15 @@ #define LUA_VERSION_MAJOR "5" -#define LUA_VERSION_MINOR "3" -#define LUA_VERSION_NUM 503 -#define LUA_VERSION_RELEASE "1" +#define LUA_VERSION_MINOR "4" +#define LUA_VERSION_RELEASE "7" + +#define LUA_VERSION_NUM 504 +#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 7) #define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE -#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2015 Lua.org, PUC-Rio" +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2024 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" @@ -49,8 +51,7 @@ #define LUA_ERRRUN 2 #define LUA_ERRSYNTAX 3 #define LUA_ERRMEM 4 -#define LUA_ERRGCMM 5 -#define LUA_ERRERR 6 +#define LUA_ERRERR 5 typedef struct lua_State lua_State; @@ -71,7 +72,7 @@ typedef struct lua_State lua_State; #define LUA_TUSERDATA 7 #define LUA_TTHREAD 8 -#define LUA_NUMTAGS 9 +#define LUA_NUMTYPES 9 @@ -124,6 +125,23 @@ typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); +/* +** Type for warning functions +*/ +typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont); + + +/* +** Type used by the debug API to collect debug information +*/ +typedef struct lua_Debug lua_Debug; + + +/* +** Functions to be called by the debugger in specific events +*/ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + /* ** generic extra include file @@ -145,11 +163,13 @@ extern const char lua_ident[]; LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); LUA_API void (lua_close) (lua_State *L); LUA_API lua_State *(lua_newthread) (lua_State *L); +LUA_API int (lua_closethread) (lua_State *L, lua_State *from); +LUA_API int (lua_resetthread) (lua_State *L); /* Deprecated! */ LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); -LUA_API const lua_Number *(lua_version) (lua_State *L); +LUA_API lua_Number (lua_version) (lua_State *L); /* @@ -182,7 +202,7 @@ LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); LUA_API int (lua_toboolean) (lua_State *L, int idx); LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); -LUA_API size_t (lua_rawlen) (lua_State *L, int idx); +LUA_API lua_Unsigned (lua_rawlen) (lua_State *L, int idx); LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); LUA_API void *(lua_touserdata) (lua_State *L, int idx); LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); @@ -247,9 +267,9 @@ LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); -LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); +LUA_API void *(lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue); LUA_API int (lua_getmetatable) (lua_State *L, int objindex); -LUA_API int (lua_getuservalue) (lua_State *L, int idx); +LUA_API int (lua_getiuservalue) (lua_State *L, int idx, int n); /* @@ -263,7 +283,7 @@ LUA_API void (lua_rawset) (lua_State *L, int idx); LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); LUA_API int (lua_setmetatable) (lua_State *L, int objindex); -LUA_API void (lua_setuservalue) (lua_State *L, int idx); +LUA_API int (lua_setiuservalue) (lua_State *L, int idx, int n); /* @@ -288,13 +308,21 @@ LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); */ LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, lua_KFunction k); -LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg, + int *nres); LUA_API int (lua_status) (lua_State *L); LUA_API int (lua_isyieldable) (lua_State *L); #define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) +/* +** Warning-related functions +*/ +LUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud); +LUA_API void (lua_warning) (lua_State *L, const char *msg, int tocont); + + /* ** garbage-collection function and options */ @@ -308,8 +336,10 @@ LUA_API int (lua_isyieldable) (lua_State *L); #define LUA_GCSETPAUSE 6 #define LUA_GCSETSTEPMUL 7 #define LUA_GCISRUNNING 9 +#define LUA_GCGEN 10 +#define LUA_GCINC 11 -LUA_API int (lua_gc) (lua_State *L, int what, int data); +LUA_API int (lua_gc) (lua_State *L, int what, ...); /* @@ -328,6 +358,8 @@ LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); +LUA_API void (lua_toclose) (lua_State *L, int idx); +LUA_API void (lua_closeslot) (lua_State *L, int idx); /* @@ -361,7 +393,7 @@ LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); #define lua_pushliteral(L, s) lua_pushstring(L, "" s) #define lua_pushglobaltable(L) \ - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) #define lua_tostring(L,i) lua_tolstring(L, (i), NULL) @@ -377,7 +409,7 @@ LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); /* ** {============================================================== -** compatibility macros for unsigned conversions +** compatibility macros ** =============================================================== */ #if defined(LUA_COMPAT_APIINTCASTS) @@ -387,6 +419,13 @@ LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); #define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) #endif + +#define lua_newuserdata(L,s) lua_newuserdatauv(L,s,1) +#define lua_getuservalue(L,idx) lua_getiuservalue(L,idx,1) +#define lua_setuservalue(L,idx) lua_setiuservalue(L,idx,1) + +#define LUA_NUMTAGS LUA_NUMTYPES + /* }============================================================== */ /* @@ -414,12 +453,6 @@ LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); #define LUA_MASKLINE (1 << LUA_HOOKLINE) #define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) -typedef struct lua_Debug lua_Debug; /* activation record */ - - -/* Functions to be called by the debugger in specific events */ -typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); - LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar); LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar); @@ -437,6 +470,7 @@ LUA_API lua_Hook (lua_gethook) (lua_State *L); LUA_API int (lua_gethookmask) (lua_State *L); LUA_API int (lua_gethookcount) (lua_State *L); +LUA_API int (lua_setcstacklimit) (lua_State *L, unsigned int limit); struct lua_Debug { int event; @@ -444,6 +478,7 @@ struct lua_Debug { const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ const char *source; /* (S) */ + size_t srclen; /* (S) */ int currentline; /* (l) */ int linedefined; /* (S) */ int lastlinedefined; /* (S) */ @@ -451,6 +486,8 @@ struct lua_Debug { unsigned char nparams;/* (u) number of parameters */ char isvararg; /* (u) */ char istailcall; /* (t) */ + unsigned short ftransfer; /* (r) index of first value transferred */ + unsigned short ntransfer; /* (r) number of transferred values */ char short_src[LUA_IDSIZE]; /* (S) */ /* private part */ struct CallInfo *i_ci; /* active function */ @@ -460,7 +497,7 @@ struct lua_Debug { /****************************************************************************** -* Copyright (C) 1994-2015 Lua.org, PUC-Rio. +* Copyright (C) 1994-2024 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/libs/lua/luaconf.h b/libs/lua/luaconf.h index 1940b74e..33bb580d 100644 --- a/libs/lua/luaconf.h +++ b/libs/lua/luaconf.h @@ -1,5 +1,5 @@ /* -** $Id: luaconf.h,v 1.251 2015/05/20 17:39:23 roberto Exp $ +** $Id: luaconf.h $ ** Configuration file for Lua ** See Copyright Notice in lua.h */ @@ -14,6 +14,16 @@ /* ** =================================================================== +** 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. ** =================================================================== */ @@ -22,20 +32,10 @@ /* ** {==================================================================== ** System Configuration: macros to adapt (if needed) Lua to some -** particular platform, for instance compiling it with 32-bit numbers or -** restricting it to C89. +** particular platform, for instance restricting it to C89. ** ===================================================================== */ -/* -@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You -** can also define LUA_32BITS in the make file, but changing here you -** ensure that all software connected to Lua will be compiled with the -** same configuration. -*/ -/* #define LUA_32BITS */ - - /* @@ LUA_USE_C89 controls the use of non-ISO-C89 features. ** Define it if you want Lua to avoid the use of a few C99 features @@ -61,45 +61,43 @@ #if defined(LUA_USE_LINUX) #define LUA_USE_POSIX #define LUA_USE_DLOPEN /* needs an extra library: -ldl */ -#define LUA_USE_READLINE /* needs some extra libraries */ #endif #if defined(LUA_USE_MACOSX) #define LUA_USE_POSIX #define LUA_USE_DLOPEN /* MacOS does not need -ldl */ -#define LUA_USE_READLINE /* needs an extra library: -lreadline */ +#endif + + +#if defined(LUA_USE_IOS) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN #endif /* -@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for -** C89 ('long' and 'double'); Windows always has '__int64', so it does -** not need to use this case. +@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits. */ -#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) -#define LUA_C89_NUMBERS -#endif +#define LUAI_IS32INT ((UINT_MAX >> 30) >= 3) + +/* }================================================================== */ /* -@@ LUAI_BITSINT defines the (minimum) number of bits in an 'int'. +** {================================================================== +** Configuration for Number types. These options should not be +** set externally, because any other code connected to Lua must +** use the same configuration. +** =================================================================== */ -/* avoid undefined shifts */ -#if ((INT_MAX >> 15) >> 15) >= 1 -#define LUAI_BITSINT 32 -#else -/* 'int' always must have at least 16 bits */ -#define LUAI_BITSINT 16 -#endif - /* @@ LUA_INT_TYPE defines the type for Lua integers. @@ LUA_FLOAT_TYPE defines the type for Lua floats. -** Lua should work fine with any mix of these options (if supported -** by your C compiler). The usual configurations are 64-bit integers +** Lua should work fine with any mix of these options supported +** by your C compiler. The usual configurations are 64-bit integers ** and 'double' (the default), 32-bit integers and 'float' (for ** restricted platforms), and 'long'/'double' (for C compilers not ** compliant with C99, which may not have support for 'long long'). @@ -115,49 +113,79 @@ #define LUA_FLOAT_DOUBLE 2 #define LUA_FLOAT_LONGDOUBLE 3 -#if defined(LUA_32BITS) /* { */ + +/* Default configuration ('long long' and 'double', for 64-bit Lua) */ +#define LUA_INT_DEFAULT LUA_INT_LONGLONG +#define LUA_FLOAT_DEFAULT LUA_FLOAT_DOUBLE + + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. +*/ +#define LUA_32BITS 0 + + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS 1 +#else +#define LUA_C89_NUMBERS 0 +#endif + + +#if LUA_32BITS /* { */ /* ** 32-bit integers and 'float' */ -#if LUAI_BITSINT >= 32 /* use 'int' if big enough */ +#if LUAI_IS32INT /* use 'int' if big enough */ #define LUA_INT_TYPE LUA_INT_INT #else /* otherwise use 'long' */ #define LUA_INT_TYPE LUA_INT_LONG #endif #define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT -#elif defined(LUA_C89_NUMBERS) /* }{ */ +#elif LUA_C89_NUMBERS /* }{ */ /* ** largest types available for C89 ('long' and 'double') */ #define LUA_INT_TYPE LUA_INT_LONG #define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE -#endif /* } */ +#else /* }{ */ +/* use defaults */ +#define LUA_INT_TYPE LUA_INT_DEFAULT +#define LUA_FLOAT_TYPE LUA_FLOAT_DEFAULT -/* -** default configuration for 64-bit Lua ('long long' and 'double') -*/ -#if !defined(LUA_INT_TYPE) -#define LUA_INT_TYPE LUA_INT_LONGLONG -#endif +#endif /* } */ -#if !defined(LUA_FLOAT_TYPE) -#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE -#endif /* } */ /* }================================================================== */ - /* ** {================================================================== ** 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. @@ -167,6 +195,7 @@ ** 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) /* { */ /* @@ -176,27 +205,40 @@ #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 /* } */ @@ -205,12 +247,25 @@ ** 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 + + +/* +** LUA_IGMARK is a mark to ignore all after it when building the +** module name (e.g., used to build the luaopen_ function name). +** Typically, the suffix after the mark is the module version, +** as in "mod-v1.2.so". +*/ +#define LUA_IGMARK "-" + /* }================================================================== */ @@ -244,16 +299,18 @@ #endif /* } */ -/* more often than not the libs go together with the core */ +/* +** More often than not the libs go together with the core. +*/ #define LUALIB_API LUA_API -#define LUAMOD_API LUALIB_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 -** that are not to be exported to outside modules (LUAI_DDEF for +@@ 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 @@ -265,12 +322,12 @@ */ #if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ defined(__ELF__) /* { */ -#define LUAI_FUNC __attribute__((visibility("hidden"))) extern +#define LUAI_FUNC __attribute__((visibility("internal"))) extern #else /* }{ */ #define LUAI_FUNC extern #endif /* } */ -#define LUAI_DDEC LUAI_FUNC +#define LUAI_DDEC(dec) LUAI_FUNC dec #define LUAI_DDEF /* empty */ /* }================================================================== */ @@ -283,88 +340,43 @@ */ /* -@@ LUA_COMPAT_5_2 controls other macros for compatibility with Lua 5.2. -@@ LUA_COMPAT_5_1 controls other macros for compatibility with Lua 5.1. +@@ 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_2) /* { */ +#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_BITLIB controls the presence of library 'bit32'. -*/ -#define LUA_COMPAT_BITLIB - -/* -@@ LUA_COMPAT_IPAIRS controls the effectiveness of the __ipairs metamethod. -*/ -#define LUA_COMPAT_IPAIRS - /* @@ 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 -#endif /* } */ - - -#if defined(LUA_COMPAT_5_1) /* { */ - -/* Incompatibilities from 5.2 -> 5.3 */ -#define LUA_COMPAT_MATHLIB -#define LUA_COMPAT_APIINTCASTS - -/* -@@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'. -** You can replace it with 'table.unpack'. -*/ -#define LUA_COMPAT_UNPACK - -/* -@@ LUA_COMPAT_LOADERS controls the presence of table 'package.loaders'. -** You can replace it with 'package.searchers'. -*/ -#define LUA_COMPAT_LOADERS - -/* -@@ macro 'lua_cpcall' emulates deprecated function lua_cpcall. -** You can call your C function directly (with light C functions). -*/ -#define lua_cpcall(L,f,u) \ - (lua_pushcfunction(L, (f)), \ - lua_pushlightuserdata(L,(u)), \ - lua_pcall(L,1,0,0)) - /* -@@ LUA_COMPAT_LOG10 defines the function 'log10' in the math library. -** You can rewrite 'log10(x)' as 'log(x, 10)'. +@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod +** using '__lt'. */ -#define LUA_COMPAT_LOG10 +#define LUA_COMPAT_LT_LE -/* -@@ LUA_COMPAT_LOADSTRING defines the function 'loadstring' in the base -** library. You can rewrite 'loadstring(s)' as 'load(s)'. -*/ -#define LUA_COMPAT_LOADSTRING - -/* -@@ LUA_COMPAT_MAXN defines the function 'maxn' in the table library. -*/ -#define LUA_COMPAT_MAXN /* @@ 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)) @@ -373,53 +385,63 @@ #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) -/* -@@ LUA_COMPAT_MODULE controls compatibility with previous -** module functions 'module' (Lua) and 'luaL_register' (C). -*/ -#define LUA_COMPAT_MODULE - #endif /* } */ - -/* -@@ LUA_COMPAT_FLOATSTRING makes Lua format integral floats without a -@@ a float mark ('.0'). -** This macro is not on by default even in compatibility mode, -** because this is not really an incompatibility. -*/ -/* #define LUA_COMPAT_FLOATSTRING */ - /* }================================================================== */ /* ** {================================================================== -** Configuration for Numbers. +** Configuration for Numbers (low-level part). ** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* ** satisfy your needs. ** =================================================================== */ /* -@@ LUA_NUMBER is the floating-point type used by Lua. -@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' @@ over a floating number. -@@ l_mathlim(x) corrects limit name 'x' to the proper float type +@@ 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. -@@ lua_str2number converts a decimal numeric string to a number. +@@ 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 */ + #if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ #define LUA_NUMBER float -#define l_mathlim(n) (FLT_##n) +#define l_floatatt(n) (FLT_##n) #define LUAI_UACNUMBER double @@ -435,7 +457,7 @@ #define LUA_NUMBER long double -#define l_mathlim(n) (LDBL_##n) +#define l_floatatt(n) (LDBL_##n) #define LUAI_UACNUMBER long double @@ -450,7 +472,7 @@ #define LUA_NUMBER double -#define l_mathlim(n) (DBL_##n) +#define l_floatatt(n) (DBL_##n) #define LUAI_UACNUMBER double @@ -468,37 +490,16 @@ #endif /* } */ -#define l_floor(x) (l_mathop(floor)(x)) - -#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) - /* -@@ lua_numbertointeger converts a float number 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)) - - - -/* -@@ LUA_INTEGER is the integer type used by Lua. -** @@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. -** -@@ LUAI_UACINT is the result of an 'usual argument conversion' -@@ over a 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_integer2str converts an integer to a string. */ @@ -506,10 +507,12 @@ /* The following definitions are good for most cases here */ #define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" -#define lua_integer2str(s,n) sprintf((s), LUA_INTEGER_FMT, (n)) #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) @@ -527,6 +530,8 @@ #define LUA_MAXINTEGER INT_MAX #define LUA_MININTEGER INT_MIN +#define LUA_MAXUNSIGNED UINT_MAX + #elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ #define LUA_INTEGER long @@ -535,8 +540,11 @@ #define LUA_MAXINTEGER LONG_MAX #define LUA_MININTEGER LONG_MIN +#define LUA_MAXUNSIGNED ULONG_MAX + #elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ #if defined(LLONG_MAX) /* { */ /* use ISO C99 stuff */ @@ -546,6 +554,8 @@ #define LUA_MAXINTEGER LLONG_MAX #define LUA_MININTEGER LLONG_MIN +#define LUA_MAXUNSIGNED ULLONG_MAX + #elif defined(LUA_USE_WINDOWS) /* }{ */ /* in Windows, can use specific Windows types */ @@ -555,6 +565,8 @@ #define LUA_MAXINTEGER _I64_MAX #define LUA_MININTEGER _I64_MIN +#define LUA_MAXUNSIGNED _UI64_MAX + #else /* }{ */ #error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ @@ -578,24 +590,43 @@ */ /* -@@ lua_strx2number converts an hexadecimal numeric string to a number. +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,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) +#define lua_strx2number(s,p) lua_str2number(s,p) #endif /* -@@ lua_number2strx converts a float to an hexadecimal numeric string. +@@ 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,f,n) sprintf(b,f,n) +#define lua_number2strx(L,b,sz,f,n) \ + ((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n))) #endif @@ -634,12 +665,40 @@ /* @@ 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 header 'locale.h'.) +** macro must include the header 'locale.h'.) */ #if !defined(lua_getlocaledecpoint) -#define 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 + + + /* }================================================================== */ @@ -675,7 +734,7 @@ ** {================================================================== ** 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). You probably do not want/need to change them. +** Lua). ** ===================================================================== */ @@ -684,8 +743,9 @@ ** 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 and max(int)/2.) */ -#if LUAI_BITSINT >= 32 +#if LUAI_IS32INT #define LUAI_MAXSTACK 1000000 #else #define LUAI_MAXSTACK 15000 @@ -702,35 +762,27 @@ /* @@ LUA_IDSIZE gives the maximum size for the description of the source -@@ of a function in debug information. +** 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. -** CHANGE it if it uses too much C-stack space. (For long double, -** 'string.format("%.99f", 1e4932)' needs ~5030 bytes, so a -** smaller buffer would force a memory allocation for each call to -** 'string.format'.) +@@ LUAL_BUFFERSIZE is the initial buffer size used by the lauxlib +** buffer system. */ -#if defined(LUA_FLOAT_LONGDOUBLE) -#define LUAL_BUFFERSIZE 8192 -#else -#define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer))) -#endif - -/* }================================================================== */ +#define LUAL_BUFFERSIZE ((int)(16 * sizeof(void*) * sizeof(lua_Number))) /* -@@ LUA_QL describes how error messages quote program elements. -** Lua does not use these macros anymore; they are here for -** compatibility only. +@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure +** maximum alignment for the other items in that union. */ -#define LUA_QL(x) "'" x "'" -#define LUA_QS LUA_QL("%s") +#define LUAI_MAXALIGN lua_Number n; double u; void *s; lua_Integer i; long l + +/* }================================================================== */ + diff --git a/libs/lua/lualib.h b/libs/lua/lualib.h index 5165c0fb..26255290 100644 --- a/libs/lua/lualib.h +++ b/libs/lua/lualib.h @@ -1,5 +1,5 @@ /* -** $Id: lualib.h,v 1.44 2014/02/06 17:32:33 roberto Exp $ +** $Id: lualib.h $ ** Lua standard libraries ** See Copyright Notice in lua.h */ @@ -11,6 +11,9 @@ #include "lua.h" +/* version suffix for environment variable names */ +#define LUA_VERSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + LUAMOD_API int (luaopen_base) (lua_State *L); @@ -32,9 +35,6 @@ LUAMOD_API int (luaopen_string) (lua_State *L); #define LUA_UTF8LIBNAME "utf8" LUAMOD_API int (luaopen_utf8) (lua_State *L); -#define LUA_BITLIBNAME "bit32" -LUAMOD_API int (luaopen_bit32) (lua_State *L); - #define LUA_MATHLIBNAME "math" LUAMOD_API int (luaopen_math) (lua_State *L); @@ -49,10 +49,4 @@ LUAMOD_API int (luaopen_package) (lua_State *L); LUALIB_API void (luaL_openlibs) (lua_State *L); - -#if !defined(lua_assert) -#define lua_assert(x) ((void)0) -#endif - - #endif diff --git a/libs/lua/lundump.c b/libs/lua/lundump.c index 510f3258..e8d92a85 100644 --- a/libs/lua/lundump.c +++ b/libs/lua/lundump.c @@ -1,5 +1,5 @@ /* -** $Id: lundump.c,v 2.41 2014/11/02 19:19:04 roberto Exp $ +** $Id: lundump.c $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -10,6 +10,7 @@ #include "lprefix.h" +#include #include #include "lua.h" @@ -25,234 +26,291 @@ #if !defined(luai_verifycode) -#define luai_verifycode(L,b,f) /* empty */ +#define luai_verifycode(L,f) /* empty */ #endif typedef struct { lua_State *L; ZIO *Z; - Mbuffer *b; const char *name; } LoadState; -static l_noret error(LoadState *S, const char *why) { - luaO_pushfstring(S->L, "%s: %s precompiled chunk", S->name, why); +static l_noret error (LoadState *S, const char *why) { + luaO_pushfstring(S->L, "%s: bad binary format (%s)", S->name, why); luaD_throw(S->L, LUA_ERRSYNTAX); } /* -** All high-level loads go through LoadVector; you can change it to +** All high-level loads go through loadVector; you can change it to ** adapt to the endianness of the input */ -#define LoadVector(S,b,n) LoadBlock(S,b,(n)*sizeof((b)[0])) +#define loadVector(S,b,n) loadBlock(S,b,(n)*sizeof((b)[0])) -static void LoadBlock (LoadState *S, void *b, size_t size) { +static void loadBlock (LoadState *S, void *b, size_t size) { if (luaZ_read(S->Z, b, size) != 0) - error(S, "truncated"); + error(S, "truncated chunk"); } -#define LoadVar(S,x) LoadVector(S,&x,1) +#define loadVar(S,x) loadVector(S,&x,1) -static lu_byte LoadByte (LoadState *S) { - lu_byte x; - LoadVar(S, x); - return x; +static lu_byte loadByte (LoadState *S) { + int b = zgetc(S->Z); + if (b == EOZ) + error(S, "truncated chunk"); + return cast_byte(b); } -static int LoadInt (LoadState *S) { - int x; - LoadVar(S, x); +static size_t loadUnsigned (LoadState *S, size_t limit) { + size_t x = 0; + int b; + limit >>= 7; + do { + b = loadByte(S); + if (x >= limit) + error(S, "integer overflow"); + x = (x << 7) | (b & 0x7f); + } while ((b & 0x80) == 0); return x; } -static lua_Number LoadNumber (LoadState *S) { +static size_t loadSize (LoadState *S) { + return loadUnsigned(S, MAX_SIZET); +} + + +static int loadInt (LoadState *S) { + return cast_int(loadUnsigned(S, INT_MAX)); +} + + +static lua_Number loadNumber (LoadState *S) { lua_Number x; - LoadVar(S, x); + loadVar(S, x); return x; } -static lua_Integer LoadInteger (LoadState *S) { +static lua_Integer loadInteger (LoadState *S) { lua_Integer x; - LoadVar(S, x); + loadVar(S, x); return x; } -static TString *LoadString (LoadState *S) { - size_t size = LoadByte(S); - if (size == 0xFF) - LoadVar(S, size); - if (size == 0) +/* +** Load a nullable string into prototype 'p'. +*/ +static TString *loadStringN (LoadState *S, Proto *p) { + lua_State *L = S->L; + TString *ts; + size_t size = loadSize(S); + if (size == 0) /* no string? */ return NULL; - else { - char *s = luaZ_openspace(S->L, S->b, --size); - LoadVector(S, s, size); - return luaS_newlstr(S->L, s, size); + else if (--size <= LUAI_MAXSHORTLEN) { /* short string? */ + char buff[LUAI_MAXSHORTLEN]; + loadVector(S, buff, size); /* load string into buffer */ + ts = luaS_newlstr(L, buff, size); /* create string */ } + else { /* long string */ + ts = luaS_createlngstrobj(L, size); /* create string */ + setsvalue2s(L, L->top.p, ts); /* anchor it ('loadVector' can GC) */ + luaD_inctop(L); + loadVector(S, getlngstr(ts), size); /* load directly in final place */ + L->top.p--; /* pop string */ + } + luaC_objbarrier(L, p, ts); + return ts; } -static void LoadCode (LoadState *S, Proto *f) { - int n = LoadInt(S); - f->code = luaM_newvector(S->L, n, Instruction); +/* +** Load a non-nullable string into prototype 'p'. +*/ +static TString *loadString (LoadState *S, Proto *p) { + TString *st = loadStringN(S, p); + if (st == NULL) + error(S, "bad format for constant string"); + return st; +} + + +static void loadCode (LoadState *S, Proto *f) { + int n = loadInt(S); + f->code = luaM_newvectorchecked(S->L, n, Instruction); f->sizecode = n; - LoadVector(S, f->code, n); + loadVector(S, f->code, n); } -static void LoadFunction(LoadState *S, Proto *f, TString *psource); +static void loadFunction(LoadState *S, Proto *f, TString *psource); -static void LoadConstants (LoadState *S, Proto *f) { +static void loadConstants (LoadState *S, Proto *f) { int i; - int n = LoadInt(S); - f->k = luaM_newvector(S->L, n, TValue); + int n = loadInt(S); + f->k = luaM_newvectorchecked(S->L, n, TValue); f->sizek = n; for (i = 0; i < n; i++) setnilvalue(&f->k[i]); for (i = 0; i < n; i++) { TValue *o = &f->k[i]; - int t = LoadByte(S); + int t = loadByte(S); switch (t) { - case LUA_TNIL: - setnilvalue(o); - break; - case LUA_TBOOLEAN: - setbvalue(o, LoadByte(S)); - break; - case LUA_TNUMFLT: - setfltvalue(o, LoadNumber(S)); - break; - case LUA_TNUMINT: - setivalue(o, LoadInteger(S)); - break; - case LUA_TSHRSTR: - case LUA_TLNGSTR: - setsvalue2n(S->L, o, LoadString(S)); - break; - default: - lua_assert(0); + case LUA_VNIL: + setnilvalue(o); + break; + case LUA_VFALSE: + setbfvalue(o); + break; + case LUA_VTRUE: + setbtvalue(o); + break; + case LUA_VNUMFLT: + setfltvalue(o, loadNumber(S)); + break; + case LUA_VNUMINT: + setivalue(o, loadInteger(S)); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + setsvalue2n(S->L, o, loadString(S, f)); + break; + default: lua_assert(0); } } } -static void LoadProtos (LoadState *S, Proto *f) { +static void loadProtos (LoadState *S, Proto *f) { int i; - int n = LoadInt(S); - f->p = luaM_newvector(S->L, n, Proto *); + int n = loadInt(S); + f->p = luaM_newvectorchecked(S->L, n, Proto *); f->sizep = n; for (i = 0; i < n; i++) f->p[i] = NULL; for (i = 0; i < n; i++) { f->p[i] = luaF_newproto(S->L); - LoadFunction(S, f->p[i], f->source); + luaC_objbarrier(S->L, f, f->p[i]); + loadFunction(S, f->p[i], f->source); } } -static void LoadUpvalues (LoadState *S, Proto *f) { +/* +** Load the upvalues for a function. The names must be filled first, +** because the filling of the other fields can raise read errors and +** the creation of the error message can call an emergency collection; +** in that case all prototypes must be consistent for the GC. +*/ +static void loadUpvalues (LoadState *S, Proto *f) { int i, n; - n = LoadInt(S); - f->upvalues = luaM_newvector(S->L, n, Upvaldesc); + n = loadInt(S); + f->upvalues = luaM_newvectorchecked(S->L, n, Upvaldesc); f->sizeupvalues = n; - for (i = 0; i < n; i++) + for (i = 0; i < n; i++) /* make array valid for GC */ f->upvalues[i].name = NULL; - for (i = 0; i < n; i++) { - f->upvalues[i].instack = LoadByte(S); - f->upvalues[i].idx = LoadByte(S); + for (i = 0; i < n; i++) { /* following calls can raise errors */ + f->upvalues[i].instack = loadByte(S); + f->upvalues[i].idx = loadByte(S); + f->upvalues[i].kind = loadByte(S); } } -static void LoadDebug (LoadState *S, Proto *f) { +static void loadDebug (LoadState *S, Proto *f) { int i, n; - n = LoadInt(S); - f->lineinfo = luaM_newvector(S->L, n, int); + n = loadInt(S); + f->lineinfo = luaM_newvectorchecked(S->L, n, ls_byte); f->sizelineinfo = n; - LoadVector(S, f->lineinfo, n); - n = LoadInt(S); - f->locvars = luaM_newvector(S->L, n, LocVar); + loadVector(S, f->lineinfo, n); + n = loadInt(S); + f->abslineinfo = luaM_newvectorchecked(S->L, n, AbsLineInfo); + f->sizeabslineinfo = n; + for (i = 0; i < n; i++) { + f->abslineinfo[i].pc = loadInt(S); + f->abslineinfo[i].line = loadInt(S); + } + n = loadInt(S); + f->locvars = luaM_newvectorchecked(S->L, n, LocVar); f->sizelocvars = n; for (i = 0; i < n; i++) f->locvars[i].varname = NULL; for (i = 0; i < n; i++) { - f->locvars[i].varname = LoadString(S); - f->locvars[i].startpc = LoadInt(S); - f->locvars[i].endpc = LoadInt(S); + f->locvars[i].varname = loadStringN(S, f); + f->locvars[i].startpc = loadInt(S); + f->locvars[i].endpc = loadInt(S); } - n = LoadInt(S); + n = loadInt(S); + if (n != 0) /* does it have debug information? */ + n = f->sizeupvalues; /* must be this many */ for (i = 0; i < n; i++) - f->upvalues[i].name = LoadString(S); + f->upvalues[i].name = loadStringN(S, f); } -static void LoadFunction (LoadState *S, Proto *f, TString *psource) { - f->source = LoadString(S); +static void loadFunction (LoadState *S, Proto *f, TString *psource) { + f->source = loadStringN(S, f); if (f->source == NULL) /* no source in dump? */ f->source = psource; /* reuse parent's source */ - f->linedefined = LoadInt(S); - f->lastlinedefined = LoadInt(S); - f->numparams = LoadByte(S); - f->is_vararg = LoadByte(S); - f->maxstacksize = LoadByte(S); - LoadCode(S, f); - LoadConstants(S, f); - LoadUpvalues(S, f); - LoadProtos(S, f); - LoadDebug(S, f); + f->linedefined = loadInt(S); + f->lastlinedefined = loadInt(S); + f->numparams = loadByte(S); + f->is_vararg = loadByte(S); + f->maxstacksize = loadByte(S); + loadCode(S, f); + loadConstants(S, f); + loadUpvalues(S, f); + loadProtos(S, f); + loadDebug(S, f); } static void checkliteral (LoadState *S, const char *s, const char *msg) { char buff[sizeof(LUA_SIGNATURE) + sizeof(LUAC_DATA)]; /* larger than both */ size_t len = strlen(s); - LoadVector(S, buff, len); + loadVector(S, buff, len); if (memcmp(s, buff, len) != 0) error(S, msg); } static void fchecksize (LoadState *S, size_t size, const char *tname) { - if (LoadByte(S) != size) - error(S, luaO_pushfstring(S->L, "%s size mismatch in", tname)); + if (loadByte(S) != size) + error(S, luaO_pushfstring(S->L, "%s size mismatch", tname)); } #define checksize(S,t) fchecksize(S,sizeof(t),#t) static void checkHeader (LoadState *S) { - checkliteral(S, LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */ - if (LoadByte(S) != LUAC_VERSION) - error(S, "version mismatch in"); - if (LoadByte(S) != LUAC_FORMAT) - error(S, "format mismatch in"); - checkliteral(S, LUAC_DATA, "corrupted"); - checksize(S, int); - checksize(S, size_t); + /* skip 1st char (already read and checked) */ + checkliteral(S, &LUA_SIGNATURE[1], "not a binary chunk"); + if (loadByte(S) != LUAC_VERSION) + error(S, "version mismatch"); + if (loadByte(S) != LUAC_FORMAT) + error(S, "format mismatch"); + checkliteral(S, LUAC_DATA, "corrupted chunk"); checksize(S, Instruction); checksize(S, lua_Integer); checksize(S, lua_Number); - if (LoadInteger(S) != LUAC_INT) - error(S, "endianness mismatch in"); - if (LoadNumber(S) != LUAC_NUM) - error(S, "float format mismatch in"); + if (loadInteger(S) != LUAC_INT) + error(S, "integer format mismatch"); + if (loadNumber(S) != LUAC_NUM) + error(S, "float format mismatch"); } /* -** load precompiled chunk +** Load precompiled chunk. */ -LClosure *luaU_undump(lua_State *L, ZIO *Z, Mbuffer *buff, - const char *name) { +LClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) { LoadState S; LClosure *cl; if (*name == '@' || *name == '=') @@ -263,15 +321,15 @@ LClosure *luaU_undump(lua_State *L, ZIO *Z, Mbuffer *buff, S.name = name; S.L = L; S.Z = Z; - S.b = buff; checkHeader(&S); - cl = luaF_newLclosure(L, LoadByte(&S)); - setclLvalue(L, L->top, cl); - incr_top(L); + cl = luaF_newLclosure(L, loadByte(&S)); + setclLvalue2s(L, L->top.p, cl); + luaD_inctop(L); cl->p = luaF_newproto(L); - LoadFunction(&S, cl->p, NULL); + luaC_objbarrier(L, cl, cl->p); + loadFunction(&S, cl->p, NULL); lua_assert(cl->nupvalues == cl->p->sizeupvalues); - luai_verifycode(L, buff, cl->p); + luai_verifycode(L, cl->p); return cl; } diff --git a/libs/lua/lundump.h b/libs/lua/lundump.h index ef43d512..a97676ca 100644 --- a/libs/lua/lundump.h +++ b/libs/lua/lundump.h @@ -1,5 +1,5 @@ /* -** $Id: lundump.h,v 1.44 2014/06/19 18:27:20 roberto Exp $ +** $Id: lundump.h $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ @@ -18,13 +18,15 @@ #define LUAC_INT 0x5678 #define LUAC_NUM cast_num(370.5) -#define MYINT(s) (s[0]-'0') -#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR)) +/* +** Encode major-minor version in one byte, one nibble for each +*/ +#define LUAC_VERSION (((LUA_VERSION_NUM / 100) * 16) + LUA_VERSION_NUM % 100) + #define LUAC_FORMAT 0 /* this is the official format */ /* load one chunk; from lundump.c */ -LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, - const char* name); +LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, const char* name); /* dump one chunk; from ldump.c */ LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, diff --git a/libs/lua/lvm.c b/libs/lua/lvm.c index a8cefc52..fcd24e11 100644 --- a/libs/lua/lvm.c +++ b/libs/lua/lvm.c @@ -1,5 +1,5 @@ /* -** $Id: lvm.c,v 2.245 2015/06/09 15:53:35 roberto Exp $ +** $Id: lvm.c $ ** Lua virtual machine ** See Copyright Notice in lua.h */ @@ -31,39 +31,72 @@ #include "lvm.h" -/* limit for table tag-method chains (to avoid loops) */ -#define MAXTAGLOOP 2000 +/* +** By default, use jump tables in the main interpreter loop on gcc +** and compatible compilers. +*/ +#if !defined(LUA_USE_JUMPTABLE) +#if defined(__GNUC__) +#define LUA_USE_JUMPTABLE 1 +#else +#define LUA_USE_JUMPTABLE 0 +#endif +#endif + +/* limit for table tag-method chains (to avoid infinite loops) */ +#define MAXTAGLOOP 2000 + /* -** 'l_intfitsf' checks whether a given integer can be converted to a -** float without rounding. Used in comparisons. Left undefined if -** all integers fit in a float precisely. +** 'l_intfitsf' checks whether a given integer is in the range that +** can be converted to a float without rounding. Used in comparisons. */ -#if !defined(l_intfitsf) /* number of bits in the mantissa of a float */ -#define NBM (l_mathlim(MANT_DIG)) +#define NBM (l_floatatt(MANT_DIG)) /* -** Check whether some integers may not fit in a float, that is, whether -** (maxinteger >> NBM) > 0 (that implies (1 << NBM) <= maxinteger). -** (The shifts are done in parts to avoid shifting by more than the size +** Check whether some integers may not fit in a float, testing whether +** (maxinteger >> NBM) > 0. (That implies (1 << NBM) <= maxinteger.) +** (The shifts are done in parts, to avoid shifting by more than the size ** of an integer. In a worst case, NBM == 113 for long double and -** sizeof(integer) == 32.) +** sizeof(long) == 32.) */ #if ((((LUA_MAXINTEGER >> (NBM / 4)) >> (NBM / 4)) >> (NBM / 4)) \ >> (NBM - (3 * (NBM / 4)))) > 0 -#define l_intfitsf(i) \ - (-((lua_Integer)1 << NBM) <= (i) && (i) <= ((lua_Integer)1 << NBM)) +/* limit for integers that fit in a float */ +#define MAXINTFITSF ((lua_Unsigned)1 << NBM) -#endif +/* check whether 'i' is in the interval [-MAXINTFITSF, MAXINTFITSF] */ +#define l_intfitsf(i) ((MAXINTFITSF + l_castS2U(i)) <= (2 * MAXINTFITSF)) + +#else /* all integers fit in a float precisely */ + +#define l_intfitsf(i) 1 #endif +/* +** Try to convert a value from string to a number value. +** If the value is not a string or is a string not representing +** a valid numeral (or if coercions from strings to numbers +** are disabled via macro 'cvt2num'), do not modify 'result' +** and return 0. +*/ +static int l_strton (const TValue *obj, TValue *result) { + lua_assert(obj != result); + if (!cvt2num(obj)) /* is object not a string? */ + return 0; + else { + TString *st = tsvalue(obj); + return (luaO_str2num(getstr(st), result) == tsslen(st) + 1); + } +} + /* ** Try to convert a value to a float. The float case is already handled @@ -75,8 +108,7 @@ int luaV_tonumber_ (const TValue *obj, lua_Number *n) { *n = cast_num(ivalue(obj)); return 1; } - else if (cvt2num(obj) && /* string convertible to number? */ - luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) { + else if (l_strton(obj, &v)) { /* string coercible to number? */ *n = nvalue(&v); /* convert result of 'luaO_str2num' to a float */ return 1; } @@ -86,170 +118,282 @@ int luaV_tonumber_ (const TValue *obj, lua_Number *n) { /* -** try to convert a value to an integer, rounding according to 'mode': -** mode == 0: accepts only integral values -** mode == 1: takes the floor of the number -** mode == 2: takes the ceil of the number +** try to convert a float to an integer, rounding according to 'mode'. */ -int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) { - TValue v; - again: - if (ttisfloat(obj)) { - lua_Number n = fltvalue(obj); - lua_Number f = l_floor(n); - if (n != f) { /* not an integral value? */ - if (mode == 0) return 0; /* fails if mode demands integral value */ - else if (mode > 1) /* needs ceil? */ - f += 1; /* convert floor to ceil (remember: n != f) */ - } - return lua_numbertointeger(f, p); +int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode) { + lua_Number f = l_floor(n); + if (n != f) { /* not an integral value? */ + if (mode == F2Ieq) return 0; /* fails if mode demands integral value */ + else if (mode == F2Iceil) /* needs ceil? */ + f += 1; /* convert floor to ceil (remember: n != f) */ } + return lua_numbertointeger(f, p); +} + + +/* +** try to convert a value to an integer, rounding according to 'mode', +** without string coercion. +** ("Fast track" handled by macro 'tointegerns'.) +*/ +int luaV_tointegerns (const TValue *obj, lua_Integer *p, F2Imod mode) { + if (ttisfloat(obj)) + return luaV_flttointeger(fltvalue(obj), p, mode); else if (ttisinteger(obj)) { *p = ivalue(obj); return 1; } - else if (cvt2num(obj) && - luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) { - obj = &v; - goto again; /* convert result from 'luaO_str2num' to an integer */ + else + return 0; +} + + +/* +** try to convert a value to an integer. +*/ +int luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode) { + TValue v; + if (l_strton(obj, &v)) /* does 'obj' point to a numerical string? */ + obj = &v; /* change it to point to its corresponding number */ + return luaV_tointegerns(obj, p, mode); +} + + +/* +** Try to convert a 'for' limit to an integer, preserving the semantics +** of the loop. Return true if the loop must not run; otherwise, '*p' +** gets the integer limit. +** (The following explanation assumes a positive step; it is valid for +** negative steps mutatis mutandis.) +** If the limit is an integer or can be converted to an integer, +** rounding down, that is the limit. +** Otherwise, check whether the limit can be converted to a float. If +** the float is too large, clip it to LUA_MAXINTEGER. If the float +** is too negative, the loop should not run, because any initial +** integer value is greater than such limit; so, the function returns +** true to signal that. (For this latter case, no integer limit would be +** correct; even a limit of LUA_MININTEGER would run the loop once for +** an initial value equal to LUA_MININTEGER.) +*/ +static int forlimit (lua_State *L, lua_Integer init, const TValue *lim, + lua_Integer *p, lua_Integer step) { + if (!luaV_tointeger(lim, p, (step < 0 ? F2Iceil : F2Ifloor))) { + /* not coercible to in integer */ + lua_Number flim; /* try to convert to float */ + if (!tonumber(lim, &flim)) /* cannot convert to float? */ + luaG_forerror(L, lim, "limit"); + /* else 'flim' is a float out of integer bounds */ + if (luai_numlt(0, flim)) { /* if it is positive, it is too large */ + if (step < 0) return 1; /* initial value must be less than it */ + *p = LUA_MAXINTEGER; /* truncate */ + } + else { /* it is less than min integer */ + if (step > 0) return 1; /* initial value must be greater than it */ + *p = LUA_MININTEGER; /* truncate */ + } } - return 0; /* conversion failed */ + return (step > 0 ? init > *p : init < *p); /* not to run? */ } /* -** Try to convert a 'for' limit to an integer, preserving the -** semantics of the loop. -** (The following explanation assumes a non-negative step; it is valid -** for negative steps mutatis mutandis.) -** If the limit can be converted to an integer, rounding down, that is -** it. -** Otherwise, check whether the limit can be converted to a number. If -** the number is too large, it is OK to set the limit as LUA_MAXINTEGER, -** which means no limit. If the number is too negative, the loop -** should not run, because any initial integer value is larger than the -** limit. So, it sets the limit to LUA_MININTEGER. 'stopnow' corrects -** the extreme case when the initial value is LUA_MININTEGER, in which -** case the LUA_MININTEGER limit would still run the loop once. +** Prepare a numerical for loop (opcode OP_FORPREP). +** Return true to skip the loop. Otherwise, +** after preparation, stack will be as follows: +** ra : internal index (safe copy of the control variable) +** ra + 1 : loop counter (integer loops) or limit (float loops) +** ra + 2 : step +** ra + 3 : control variable */ -static int forlimit (const TValue *obj, lua_Integer *p, lua_Integer step, - int *stopnow) { - *stopnow = 0; /* usually, let loops run */ - if (!luaV_tointeger(obj, p, (step < 0 ? 2 : 1))) { /* not fit in integer? */ - lua_Number n; /* try to convert to float */ - if (!tonumber(obj, &n)) /* cannot convert to float? */ - return 0; /* not a number */ - if (luai_numlt(0, n)) { /* if true, float is larger than max integer */ - *p = LUA_MAXINTEGER; - if (step < 0) *stopnow = 1; +static int forprep (lua_State *L, StkId ra) { + TValue *pinit = s2v(ra); + TValue *plimit = s2v(ra + 1); + TValue *pstep = s2v(ra + 2); + if (ttisinteger(pinit) && ttisinteger(pstep)) { /* integer loop? */ + lua_Integer init = ivalue(pinit); + lua_Integer step = ivalue(pstep); + lua_Integer limit; + if (step == 0) + luaG_runerror(L, "'for' step is zero"); + setivalue(s2v(ra + 3), init); /* control variable */ + if (forlimit(L, init, plimit, &limit, step)) + return 1; /* skip the loop */ + else { /* prepare loop counter */ + lua_Unsigned count; + if (step > 0) { /* ascending loop? */ + count = l_castS2U(limit) - l_castS2U(init); + if (step != 1) /* avoid division in the too common case */ + count /= l_castS2U(step); + } + else { /* step < 0; descending loop */ + count = l_castS2U(init) - l_castS2U(limit); + /* 'step+1' avoids negating 'mininteger' */ + count /= l_castS2U(-(step + 1)) + 1u; + } + /* store the counter in place of the limit (which won't be + needed anymore) */ + setivalue(plimit, l_castU2S(count)); } - else { /* float is smaller than min integer */ - *p = LUA_MININTEGER; - if (step >= 0) *stopnow = 1; + } + else { /* try making all values floats */ + lua_Number init; lua_Number limit; lua_Number step; + if (l_unlikely(!tonumber(plimit, &limit))) + luaG_forerror(L, plimit, "limit"); + if (l_unlikely(!tonumber(pstep, &step))) + luaG_forerror(L, pstep, "step"); + if (l_unlikely(!tonumber(pinit, &init))) + luaG_forerror(L, pinit, "initial value"); + if (step == 0) + luaG_runerror(L, "'for' step is zero"); + if (luai_numlt(0, step) ? luai_numlt(limit, init) + : luai_numlt(init, limit)) + return 1; /* skip the loop */ + else { + /* make sure internal values are all floats */ + setfltvalue(plimit, limit); + setfltvalue(pstep, step); + setfltvalue(s2v(ra), init); /* internal index */ + setfltvalue(s2v(ra + 3), init); /* control variable */ } } - return 1; + return 0; +} + + +/* +** Execute a step of a float numerical for loop, returning +** true iff the loop must continue. (The integer case is +** written online with opcode OP_FORLOOP, for performance.) +*/ +static int floatforloop (StkId ra) { + lua_Number step = fltvalue(s2v(ra + 2)); + lua_Number limit = fltvalue(s2v(ra + 1)); + lua_Number idx = fltvalue(s2v(ra)); /* internal index */ + idx = luai_numadd(L, idx, step); /* increment index */ + if (luai_numlt(0, step) ? luai_numle(idx, limit) + : luai_numle(limit, idx)) { + chgfltvalue(s2v(ra), idx); /* update internal index */ + setfltvalue(s2v(ra + 3), idx); /* and control variable */ + return 1; /* jump back */ + } + else + return 0; /* finish the loop */ } /* -** Main function for table access (invoking metamethods if needed). -** Compute 'val = t[key]' +** Finish the table access 'val = t[key]'. +** if 'slot' is NULL, 't' is not a table; otherwise, 'slot' points to +** t[k] entry (which must be empty). */ -void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { +void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, + const TValue *slot) { int loop; /* counter to avoid infinite loops */ + const TValue *tm; /* metamethod */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* 't' is a table? */ - Table *h = hvalue(t); - const TValue *res = luaH_get(h, key); /* do a primitive get */ - if (!ttisnil(res) || /* result is not nil? */ - (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ - setobj2s(L, val, res); /* result is the raw get */ + if (slot == NULL) { /* 't' is not a table? */ + lua_assert(!ttistable(t)); + tm = luaT_gettmbyobj(L, t, TM_INDEX); + if (l_unlikely(notm(tm))) + luaG_typeerror(L, t, "index"); /* no metamethod */ + /* else will try the metamethod */ + } + else { /* 't' is a table */ + lua_assert(isempty(slot)); + tm = fasttm(L, hvalue(t)->metatable, TM_INDEX); /* table's metamethod */ + if (tm == NULL) { /* no metamethod? */ + setnilvalue(s2v(val)); /* result is nil */ return; } - /* else will try metamethod */ + /* else will try the metamethod */ } - else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) - luaG_typeerror(L, t, "index"); /* no metamethod */ - if (ttisfunction(tm)) { /* metamethod is a function */ - luaT_callTM(L, tm, t, key, val, 1); + if (ttisfunction(tm)) { /* is metamethod a function? */ + luaT_callTMres(L, tm, t, key, val); /* call it */ return; } - t = tm; /* else repeat access over 'tm' */ + t = tm; /* else try to access 'tm[key]' */ + if (luaV_fastget(L, t, key, slot, luaH_get)) { /* fast track? */ + setobj2s(L, val, slot); /* done */ + return; + } + /* else repeat (tail call 'luaV_finishget') */ } - luaG_runerror(L, "gettable chain too long; possible loop"); + luaG_runerror(L, "'__index' chain too long; possible loop"); } /* -** Main function for table assignment (invoking metamethods if needed). -** Compute 't[key] = val' +** Finish a table assignment 't[key] = val'. +** If 'slot' is NULL, 't' is not a table. Otherwise, 'slot' points +** to the entry 't[key]', or to a value with an absent key if there +** is no such entry. (The value at 'slot' must be empty, otherwise +** 'luaV_fastget' would have done the job.) */ -void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { +void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + TValue *val, const TValue *slot) { int loop; /* counter to avoid infinite loops */ for (loop = 0; loop < MAXTAGLOOP; loop++) { - const TValue *tm; - if (ttistable(t)) { /* 't' is a table? */ - Table *h = hvalue(t); - TValue *oldval = cast(TValue *, luaH_get(h, key)); - /* if previous value is not nil, there must be a previous entry - in the table; a metamethod has no relevance */ - if (!ttisnil(oldval) || - /* previous value is nil; must check the metamethod */ - ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL && - /* no metamethod; is there a previous entry in the table? */ - (oldval != luaO_nilobject || - /* no previous entry; must create one. (The next test is - always true; we only need the assignment.) */ - (oldval = luaH_newkey(L, h, key), 1)))) { - /* no metamethod and (now) there is an entry with given key */ - setobj2t(L, oldval, val); /* assign new value to that entry */ + const TValue *tm; /* '__newindex' metamethod */ + if (slot != NULL) { /* is 't' a table? */ + Table *h = hvalue(t); /* save 't' table */ + lua_assert(isempty(slot)); /* slot must be empty */ + tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */ + if (tm == NULL) { /* no metamethod? */ + luaH_finishset(L, h, key, slot, val); /* set new value */ invalidateTMcache(h); - luaC_barrierback(L, h, val); + luaC_barrierback(L, obj2gco(h), val); return; } /* else will try the metamethod */ } - else /* not a table; check metamethod */ - if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) + else { /* not a table; check metamethod */ + tm = luaT_gettmbyobj(L, t, TM_NEWINDEX); + if (l_unlikely(notm(tm))) luaG_typeerror(L, t, "index"); + } /* try the metamethod */ if (ttisfunction(tm)) { - luaT_callTM(L, tm, t, key, val, 0); + luaT_callTM(L, tm, t, key, val); return; } t = tm; /* else repeat assignment over 'tm' */ + if (luaV_fastget(L, t, key, slot, luaH_get)) { + luaV_finishfastset(L, t, slot, val); + return; /* done */ + } + /* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */ } - luaG_runerror(L, "settable chain too long; possible loop"); + luaG_runerror(L, "'__newindex' chain too long; possible loop"); } /* -** Compare two strings 'ls' x 'rs', returning an integer smaller-equal- -** -larger than zero if 'ls' is smaller-equal-larger than 'rs'. +** Compare two strings 'ts1' x 'ts2', returning an integer less-equal- +** -greater than zero if 'ts1' is less-equal-greater than 'ts2'. ** The code is a little tricky because it allows '\0' in the strings -** and it uses 'strcoll' (to respect locales) for each segments -** of the strings. +** and it uses 'strcoll' (to respect locales) for each segment +** of the strings. Note that segments can compare equal but still +** have different lengths. */ -static int l_strcmp (const TString *ls, const TString *rs) { - const char *l = getstr(ls); - size_t ll = tsslen(ls); - const char *r = getstr(rs); - size_t lr = tsslen(rs); +static int l_strcmp (const TString *ts1, const TString *ts2) { + const char *s1 = getstr(ts1); + size_t rl1 = tsslen(ts1); /* real length */ + const char *s2 = getstr(ts2); + size_t rl2 = tsslen(ts2); for (;;) { /* for each segment */ - int temp = strcoll(l, r); + int temp = strcoll(s1, s2); if (temp != 0) /* not equal? */ return temp; /* done */ else { /* strings are equal up to a '\0' */ - size_t len = strlen(l); /* index of first '\0' in both strings */ - if (len == lr) /* 'rs' is finished? */ - return (len == ll) ? 0 : 1; /* check 'ls' */ - else if (len == ll) /* 'ls' is finished? */ - return -1; /* 'ls' is smaller than 'rs' ('rs' is not finished) */ - /* both strings longer than 'len'; go on comparing after the '\0' */ - len++; - l += len; ll -= len; r += len; lr -= len; + size_t zl1 = strlen(s1); /* index of first '\0' in 's1' */ + size_t zl2 = strlen(s2); /* index of first '\0' in 's2' */ + if (zl2 == rl2) /* 's2' is finished? */ + return (zl1 == rl1) ? 0 : 1; /* check 's1' */ + else if (zl1 == rl1) /* 's1' is finished? */ + return -1; /* 's1' is less than 's2' ('s2' is not finished) */ + /* both strings longer than 'zl'; go on comparing after the '\0' */ + zl1++; zl2++; + s1 += zl1; rl1 -= zl1; s2 += zl2; rl2 -= zl2; } } } @@ -258,25 +402,24 @@ static int l_strcmp (const TString *ls, const TString *rs) { /* ** Check whether integer 'i' is less than float 'f'. If 'i' has an ** exact representation as a float ('l_intfitsf'), compare numbers as -** floats. Otherwise, if 'f' is outside the range for integers, result -** is trivial. Otherwise, compare them as integers. (When 'i' has no -** float representation, either 'f' is "far away" from 'i' or 'f' has -** no precision left for a fractional part; either way, how 'f' is -** truncated is irrelevant.) When 'f' is NaN, comparisons must result -** in false. +** floats. Otherwise, use the equivalence 'i < f <=> i < ceil(f)'. +** If 'ceil(f)' is out of integer range, either 'f' is greater than +** all integers or less than all integers. +** (The test with 'l_intfitsf' is only for performance; the else +** case is correct for all values, but it is slow due to the conversion +** from float to int.) +** When 'f' is NaN, comparisons must result in false. */ -static int LTintfloat (lua_Integer i, lua_Number f) { -#if defined(l_intfitsf) - if (!l_intfitsf(i)) { - if (f >= -cast_num(LUA_MININTEGER)) /* -minint == maxint + 1 */ - return 1; /* f >= maxint + 1 > i */ - else if (f > cast_num(LUA_MININTEGER)) /* minint < f <= maxint ? */ - return (i < cast(lua_Integer, f)); /* compare them as integers */ - else /* f <= minint <= i (or 'f' is NaN) --> not(i < f) */ - return 0; +l_sinline int LTintfloat (lua_Integer i, lua_Number f) { + if (l_intfitsf(i)) + return luai_numlt(cast_num(i), f); /* compare them as floats */ + else { /* i < f <=> i < ceil(f) */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Iceil)) /* fi = ceil(f) */ + return i < fi; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f > 0; /* greater? */ } -#endif - return luai_numlt(cast_num(i), f); /* compare them as floats */ } @@ -284,25 +427,58 @@ static int LTintfloat (lua_Integer i, lua_Number f) { ** Check whether integer 'i' is less than or equal to float 'f'. ** See comments on previous function. */ -static int LEintfloat (lua_Integer i, lua_Number f) { -#if defined(l_intfitsf) - if (!l_intfitsf(i)) { - if (f >= -cast_num(LUA_MININTEGER)) /* -minint == maxint + 1 */ - return 1; /* f >= maxint + 1 > i */ - else if (f >= cast_num(LUA_MININTEGER)) /* minint <= f <= maxint ? */ - return (i <= cast(lua_Integer, f)); /* compare them as integers */ - else /* f < minint <= i (or 'f' is NaN) --> not(i <= f) */ - return 0; +l_sinline int LEintfloat (lua_Integer i, lua_Number f) { + if (l_intfitsf(i)) + return luai_numle(cast_num(i), f); /* compare them as floats */ + else { /* i <= f <=> i <= floor(f) */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ifloor)) /* fi = floor(f) */ + return i <= fi; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f > 0; /* greater? */ + } +} + + +/* +** Check whether float 'f' is less than integer 'i'. +** See comments on previous function. +*/ +l_sinline int LTfloatint (lua_Number f, lua_Integer i) { + if (l_intfitsf(i)) + return luai_numlt(f, cast_num(i)); /* compare them as floats */ + else { /* f < i <=> floor(f) < i */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ifloor)) /* fi = floor(f) */ + return fi < i; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f < 0; /* less? */ + } +} + + +/* +** Check whether float 'f' is less than or equal to integer 'i'. +** See comments on previous function. +*/ +l_sinline int LEfloatint (lua_Number f, lua_Integer i) { + if (l_intfitsf(i)) + return luai_numle(f, cast_num(i)); /* compare them as floats */ + else { /* f <= i <=> ceil(f) <= i */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Iceil)) /* fi = ceil(f) */ + return fi <= i; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f < 0; /* less? */ } -#endif - return luai_numle(cast_num(i), f); /* compare them as floats */ } /* ** Return 'l < r', for numbers. */ -static int LTnum (const TValue *l, const TValue *r) { +l_sinline int LTnum (const TValue *l, const TValue *r) { + lua_assert(ttisnumber(l) && ttisnumber(r)); if (ttisinteger(l)) { lua_Integer li = ivalue(l); if (ttisinteger(r)) @@ -314,10 +490,8 @@ static int LTnum (const TValue *l, const TValue *r) { lua_Number lf = fltvalue(l); /* 'l' must be float */ if (ttisfloat(r)) return luai_numlt(lf, fltvalue(r)); /* both are float */ - else if (luai_numisnan(lf)) /* 'r' is int and 'l' is float */ - return 0; /* NaN < i is always false */ - else /* without NaN, (l < r) <--> not(r <= l) */ - return !LEintfloat(ivalue(r), lf); /* not (r <= l) ? */ + else /* 'l' is float and 'r' is int */ + return LTfloatint(lf, ivalue(r)); } } @@ -325,7 +499,8 @@ static int LTnum (const TValue *l, const TValue *r) { /* ** Return 'l <= r', for numbers. */ -static int LEnum (const TValue *l, const TValue *r) { +l_sinline int LEnum (const TValue *l, const TValue *r) { + lua_assert(ttisnumber(l) && ttisnumber(r)); if (ttisinteger(l)) { lua_Integer li = ivalue(l); if (ttisinteger(r)) @@ -337,53 +512,53 @@ static int LEnum (const TValue *l, const TValue *r) { lua_Number lf = fltvalue(l); /* 'l' must be float */ if (ttisfloat(r)) return luai_numle(lf, fltvalue(r)); /* both are float */ - else if (luai_numisnan(lf)) /* 'r' is int and 'l' is float */ - return 0; /* NaN <= i is always false */ - else /* without NaN, (l <= r) <--> not(r < l) */ - return !LTintfloat(ivalue(r), lf); /* not (r < l) ? */ + else /* 'l' is float and 'r' is int */ + return LEfloatint(lf, ivalue(r)); } } +/* +** return 'l < r' for non-numbers. +*/ +static int lessthanothers (lua_State *L, const TValue *l, const TValue *r) { + lua_assert(!ttisnumber(l) || !ttisnumber(r)); + if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) < 0; + else + return luaT_callorderTM(L, l, r, TM_LT); +} + + /* ** Main operation less than; return 'l < r'. */ int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { - int res; if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ return LTnum(l, r); - else if (ttisstring(l) && ttisstring(r)) /* both are strings? */ - return l_strcmp(tsvalue(l), tsvalue(r)) < 0; - else if ((res = luaT_callorderTM(L, l, r, TM_LT)) < 0) /* no metamethod? */ - luaG_ordererror(L, l, r); /* error */ - return res; + else return lessthanothers(L, l, r); +} + + +/* +** return 'l <= r' for non-numbers. +*/ +static int lessequalothers (lua_State *L, const TValue *l, const TValue *r) { + lua_assert(!ttisnumber(l) || !ttisnumber(r)); + if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; + else + return luaT_callorderTM(L, l, r, TM_LE); } /* -** Main operation less than or equal to; return 'l <= r'. If it needs -** a metamethod and there is no '__le', try '__lt', based on -** l <= r iff !(r < l) (assuming a total order). If the metamethod -** yields during this substitution, the continuation has to know -** about it (to negate the result of r= 0) /* try 'le' */ - return res; - else { /* try 'lt': */ - L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ - res = luaT_callorderTM(L, r, l, TM_LT); - L->ci->callstatus ^= CIST_LEQ; /* clear mark */ - if (res < 0) - luaG_ordererror(L, l, r); - return !res; /* result is negated */ - } + else return lessequalothers(L, l, r); } @@ -393,25 +568,29 @@ int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { */ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { const TValue *tm; - if (ttype(t1) != ttype(t2)) { /* not the same variant? */ - if (ttnov(t1) != ttnov(t2) || ttnov(t1) != LUA_TNUMBER) + if (ttypetag(t1) != ttypetag(t2)) { /* not the same variant? */ + if (ttype(t1) != ttype(t2) || ttype(t1) != LUA_TNUMBER) return 0; /* only numbers can be equal with different variants */ else { /* two numbers with different variants */ - lua_Integer i1, i2; /* compare them as integers */ - return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); + /* One of them is an integer. If the other does not have an + integer value, they cannot be equal; otherwise, compare their + integer values. */ + lua_Integer i1, i2; + return (luaV_tointegerns(t1, &i1, F2Ieq) && + luaV_tointegerns(t2, &i2, F2Ieq) && + i1 == i2); } } /* values have same type and same variant */ - switch (ttype(t1)) { - case LUA_TNIL: return 1; - case LUA_TNUMINT: return (ivalue(t1) == ivalue(t2)); - case LUA_TNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); - case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ - case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); - case LUA_TLCF: return fvalue(t1) == fvalue(t2); - case LUA_TSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2)); - case LUA_TLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2)); - case LUA_TUSERDATA: { + switch (ttypetag(t1)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: return 1; + case LUA_VNUMINT: return (ivalue(t1) == ivalue(t2)); + case LUA_VNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); + case LUA_VLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); + case LUA_VLCF: return fvalue(t1) == fvalue(t2); + case LUA_VSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2)); + case LUA_VLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2)); + case LUA_VUSERDATA: { if (uvalue(t1) == uvalue(t2)) return 1; else if (L == NULL) return 0; tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); @@ -419,7 +598,7 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } - case LUA_TTABLE: { + case LUA_VTABLE: { if (hvalue(t1) == hvalue(t2)) return 1; else if (L == NULL) return 0; tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); @@ -432,8 +611,10 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { } if (tm == NULL) /* no TM? */ return 0; /* objects are different */ - luaT_callTM(L, tm, t1, t2, L->top, 1); /* call TM */ - return !l_isfalse(L->top); + else { + luaT_callTMres(L, tm, t1, t2, L->top.p); /* call TM */ + return !l_isfalse(s2v(L->top.p)); + } } @@ -443,79 +624,95 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { #define isemptystr(o) (ttisshrstring(o) && tsvalue(o)->shrlen == 0) +/* copy strings in stack from top - n up to top - 1 to buffer */ +static void copy2buff (StkId top, int n, char *buff) { + size_t tl = 0; /* size already copied */ + do { + TString *st = tsvalue(s2v(top - n)); + size_t l = tsslen(st); /* length of string being copied */ + memcpy(buff + tl, getstr(st), l * sizeof(char)); + tl += l; + } while (--n > 0); +} + + /* ** Main operation for concatenation: concat 'total' values in the stack, -** from 'L->top - total' up to 'L->top - 1'. +** from 'L->top.p - total' up to 'L->top.p - 1'. */ void luaV_concat (lua_State *L, int total) { - lua_assert(total >= 2); + if (total == 1) + return; /* "all" values already concatenated */ do { - StkId top = L->top; + StkId top = L->top.p; int n = 2; /* number of elements handled in this pass (at least 2) */ - if (!(ttisstring(top-2) || cvt2str(top-2)) || !tostring(L, top-1)) - luaT_trybinTM(L, top-2, top-1, top-2, TM_CONCAT); - else if (isemptystr(top - 1)) /* second operand is empty? */ - cast_void(tostring(L, top - 2)); /* result is first operand */ - else if (isemptystr(top - 2)) { /* first operand is an empty string? */ + if (!(ttisstring(s2v(top - 2)) || cvt2str(s2v(top - 2))) || + !tostring(L, s2v(top - 1))) + luaT_tryconcatTM(L); /* may invalidate 'top' */ + else if (isemptystr(s2v(top - 1))) /* second operand is empty? */ + cast_void(tostring(L, s2v(top - 2))); /* result is first operand */ + else if (isemptystr(s2v(top - 2))) { /* first operand is empty string? */ setobjs2s(L, top - 2, top - 1); /* result is second op. */ } else { /* at least two non-empty string values; get as many as possible */ - size_t tl = vslen(top - 1); - char *buffer; - int i; - /* collect total length */ - for (i = 1; i < total && tostring(L, top-i-1); i++) { - size_t l = vslen(top - i - 1); - if (l >= (MAX_SIZE/sizeof(char)) - tl) + size_t tl = tsslen(tsvalue(s2v(top - 1))); + TString *ts; + /* collect total length and number of strings */ + for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++) { + size_t l = tsslen(tsvalue(s2v(top - n - 1))); + if (l_unlikely(l >= MAX_SIZE - sizeof(TString) - tl)) { + L->top.p = top - total; /* pop strings to avoid wasting stack */ luaG_runerror(L, "string length overflow"); + } tl += l; } - buffer = luaZ_openspace(L, &G(L)->buff, tl); - tl = 0; - n = i; - do { /* copy all strings to buffer */ - size_t l = vslen(top - i); - memcpy(buffer+tl, svalue(top-i), l * sizeof(char)); - tl += l; - } while (--i > 0); - setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); /* create result */ + if (tl <= LUAI_MAXSHORTLEN) { /* is result a short string? */ + char buff[LUAI_MAXSHORTLEN]; + copy2buff(top, n, buff); /* copy strings to buffer */ + ts = luaS_newlstr(L, buff, tl); + } + else { /* long string; copy strings directly to final result */ + ts = luaS_createlngstrobj(L, tl); + copy2buff(top, n, getlngstr(ts)); + } + setsvalue2s(L, top - n, ts); /* create result */ } - total -= n-1; /* got 'n' strings to create 1 new */ - L->top -= n-1; /* popped 'n' strings and pushed one */ + total -= n - 1; /* got 'n' strings to create one new */ + L->top.p -= n - 1; /* popped 'n' strings and pushed one */ } while (total > 1); /* repeat until only 1 result left */ } /* -** Main operation 'ra' = #rb'. +** Main operation 'ra = #rb'. */ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { const TValue *tm; - switch (ttype(rb)) { - case LUA_TTABLE: { + switch (ttypetag(rb)) { + case LUA_VTABLE: { Table *h = hvalue(rb); tm = fasttm(L, h->metatable, TM_LEN); if (tm) break; /* metamethod? break switch to call it */ - setivalue(ra, luaH_getn(h)); /* else primitive len */ + setivalue(s2v(ra), luaH_getn(h)); /* else primitive len */ return; } - case LUA_TSHRSTR: { - setivalue(ra, tsvalue(rb)->shrlen); + case LUA_VSHRSTR: { + setivalue(s2v(ra), tsvalue(rb)->shrlen); return; } - case LUA_TLNGSTR: { - setivalue(ra, tsvalue(rb)->u.lnglen); + case LUA_VLNGSTR: { + setivalue(s2v(ra), tsvalue(rb)->u.lnglen); return; } default: { /* try metamethod */ tm = luaT_gettmbyobj(L, rb, TM_LEN); - if (ttisnil(tm)) /* no metamethod? */ + if (l_unlikely(notm(tm))) /* no metamethod? */ luaG_typeerror(L, rb, "get length of"); break; } } - luaT_callTM(L, tm, rb, rb, ra, 1); + luaT_callTMres(L, tm, rb, rb, ra); } @@ -525,8 +722,8 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { ** 'floor(q) == trunc(q)' when 'q >= 0' or when 'q' is integer, ** otherwise 'floor(q) == trunc(q) - 1'. */ -lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) { - if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */ +lua_Integer luaV_idiv (lua_State *L, lua_Integer m, lua_Integer n) { + if (l_unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */ if (n == 0) luaG_runerror(L, "attempt to divide by zero"); return intop(-, 0, m); /* n==-1; avoid overflow with 0x80000...//-1 */ @@ -543,26 +740,37 @@ lua_Integer luaV_div (lua_State *L, lua_Integer m, lua_Integer n) { /* ** Integer modulus; return 'm % n'. (Assume that C '%' with ** negative operands follows C99 behavior. See previous comment -** about luaV_div.) +** about luaV_idiv.) */ lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) { - if (l_castS2U(n) + 1u <= 1u) { /* special cases: -1 or 0 */ + if (l_unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */ if (n == 0) luaG_runerror(L, "attempt to perform 'n%%0'"); return 0; /* m % -1 == 0; avoid overflow with 0x80000...%-1 */ } else { lua_Integer r = m % n; - if (r != 0 && (m ^ n) < 0) /* 'm/n' would be non-integer negative? */ + if (r != 0 && (r ^ n) < 0) /* 'm/n' would be non-integer negative? */ r += n; /* correct result for different rounding */ return r; } } +/* +** Float modulus +*/ +lua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) { + lua_Number r; + luai_nummod(L, m, n, r); + return r; +} + + /* number of bits in an integer */ #define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT) + /* ** Shift left operation. (Shift right just negates 'y'.) */ @@ -578,32 +786,9 @@ lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) { } -/* -** check whether cached closure in prototype 'p' may be reused, that is, -** whether there is a cached closure with the same upvalues needed by -** new closure to be created. -*/ -static LClosure *getcached (Proto *p, UpVal **encup, StkId base) { - LClosure *c = p->cache; - if (c != NULL) { /* is there a cached closure? */ - int nup = p->sizeupvalues; - Upvaldesc *uv = p->upvalues; - int i; - for (i = 0; i < nup; i++) { /* check whether it has right upvalues */ - TValue *v = uv[i].instack ? base + uv[i].idx : encup[uv[i].idx]->v; - if (c->upvals[i]->v != v) - return NULL; /* wrong upvalue; cannot reuse closure */ - } - } - return c; /* return cached closure (or NULL if no cached closure) */ -} - - /* ** create a new Lua closure, push it in the stack, and initialize -** its upvalues. Note that the closure is not cached if prototype is -** already black (which means that 'cache' was already cleared by the -** GC). +** its upvalues. */ static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, StkId ra) { @@ -612,77 +797,82 @@ static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, int i; LClosure *ncl = luaF_newLclosure(L, nup); ncl->p = p; - setclLvalue(L, ra, ncl); /* anchor new closure in stack */ + setclLvalue2s(L, ra, ncl); /* anchor new closure in stack */ for (i = 0; i < nup; i++) { /* fill in its upvalues */ if (uv[i].instack) /* upvalue refers to local variable? */ ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx); else /* get upvalue from enclosing function */ ncl->upvals[i] = encup[uv[i].idx]; - ncl->upvals[i]->refcount++; - /* new closure is white, so we do not need a barrier here */ + luaC_objbarrier(L, ncl, ncl->upvals[i]); } - if (!isblack(p)) /* cache will not break GC invariant? */ - p->cache = ncl; /* save it on cache for reuse */ } /* -** finish execution of an opcode interrupted by an yield +** finish execution of an opcode interrupted by a yield */ void luaV_finishOp (lua_State *L) { CallInfo *ci = L->ci; - StkId base = ci->u.l.base; + StkId base = ci->func.p + 1; Instruction inst = *(ci->u.l.savedpc - 1); /* interrupted instruction */ OpCode op = GET_OPCODE(inst); switch (op) { /* finish its execution */ - case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_IDIV: - case OP_BAND: case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: - case OP_MOD: case OP_POW: + case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: { + setobjs2s(L, base + GETARG_A(*(ci->u.l.savedpc - 2)), --L->top.p); + break; + } case OP_UNM: case OP_BNOT: case OP_LEN: - case OP_GETTABUP: case OP_GETTABLE: case OP_SELF: { - setobjs2s(L, base + GETARG_A(inst), --L->top); + case OP_GETTABUP: case OP_GETTABLE: case OP_GETI: + case OP_GETFIELD: case OP_SELF: { + setobjs2s(L, base + GETARG_A(inst), --L->top.p); break; } - case OP_LE: case OP_LT: case OP_EQ: { - int res = !l_isfalse(L->top - 1); - L->top--; + case OP_LT: case OP_LE: + case OP_LTI: case OP_LEI: + case OP_GTI: case OP_GEI: + case OP_EQ: { /* note that 'OP_EQI'/'OP_EQK' cannot yield */ + int res = !l_isfalse(s2v(L->top.p - 1)); + L->top.p--; +#if defined(LUA_COMPAT_LT_LE) if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ - lua_assert(op == OP_LE); ci->callstatus ^= CIST_LEQ; /* clear mark */ res = !res; /* negate result */ } +#endif lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); - if (res != GETARG_A(inst)) /* condition failed? */ + if (res != GETARG_k(inst)) /* condition failed? */ ci->u.l.savedpc++; /* skip jump instruction */ break; } case OP_CONCAT: { - StkId top = L->top - 1; /* top when 'luaT_trybinTM' was called */ - int b = GETARG_B(inst); /* first element to concatenate */ - int total = cast_int(top - 1 - (base + b)); /* yet to concatenate */ - setobj2s(L, top - 2, top); /* put TM result in proper position */ - if (total > 1) { /* are there elements to concat? */ - L->top = top - 1; /* top is one after last element (at top-2) */ - luaV_concat(L, total); /* concat them (may yield again) */ - } - /* move final result to final position */ - setobj2s(L, ci->u.l.base + GETARG_A(inst), L->top - 1); - L->top = ci->top; /* restore top */ + StkId top = L->top.p - 1; /* top when 'luaT_tryconcatTM' was called */ + int a = GETARG_A(inst); /* first element to concatenate */ + int total = cast_int(top - 1 - (base + a)); /* yet to concatenate */ + setobjs2s(L, top - 2, top); /* put TM result in proper position */ + L->top.p = top - 1; /* top is one after last element (at top-2) */ + luaV_concat(L, total); /* concat them (may yield again) */ break; } - case OP_TFORCALL: { - lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_TFORLOOP); - L->top = ci->top; /* correct top */ + case OP_CLOSE: { /* yielded closing variables */ + ci->u.l.savedpc--; /* repeat instruction to close other vars. */ break; } - case OP_CALL: { - if (GETARG_C(inst) - 1 >= 0) /* nresults >= 0? */ - L->top = ci->top; /* adjust results */ + case OP_RETURN: { /* yielded closing variables */ + StkId ra = base + GETARG_A(inst); + /* adjust top to signal correct number of returns, in case the + return is "up to top" ('isIT') */ + L->top.p = ra + ci->u2.nres; + /* repeat instruction to close other vars. and complete the return */ + ci->u.l.savedpc--; break; } - case OP_TAILCALL: case OP_SETTABUP: case OP_SETTABLE: + default: { + /* only these other opcodes can yield */ + lua_assert(op == OP_TFORCALL || op == OP_CALL || + op == OP_TAILCALL || op == OP_SETTABUP || op == OP_SETTABLE || + op == OP_SETI || op == OP_SETFIELD); break; - default: lua_assert(0); + } } } @@ -691,340 +881,719 @@ void luaV_finishOp (lua_State *L) { /* ** {================================================================== -** Function 'luaV_execute': main interpreter loop +** Macros for arithmetic/bitwise/comparison opcodes in 'luaV_execute' ** =================================================================== */ +#define l_addi(L,a,b) intop(+, a, b) +#define l_subi(L,a,b) intop(-, a, b) +#define l_muli(L,a,b) intop(*, a, b) +#define l_band(a,b) intop(&, a, b) +#define l_bor(a,b) intop(|, a, b) +#define l_bxor(a,b) intop(^, a, b) + +#define l_lti(a,b) (a < b) +#define l_lei(a,b) (a <= b) +#define l_gti(a,b) (a > b) +#define l_gei(a,b) (a >= b) + /* -** some macros for common tasks in 'luaV_execute' +** Arithmetic operations with immediate operands. 'iop' is the integer +** operation, 'fop' is the float operation. */ +#define op_arithI(L,iop,fop) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + int imm = GETARG_sC(i); \ + if (ttisinteger(v1)) { \ + lua_Integer iv1 = ivalue(v1); \ + pc++; setivalue(s2v(ra), iop(L, iv1, imm)); \ + } \ + else if (ttisfloat(v1)) { \ + lua_Number nb = fltvalue(v1); \ + lua_Number fimm = cast_num(imm); \ + pc++; setfltvalue(s2v(ra), fop(L, nb, fimm)); \ + }} -#if !defined(luai_runtimecheck) -#define luai_runtimecheck(L, c) /* void */ -#endif + +/* +** Auxiliary function for arithmetic operations over floats and others +** with two register operands. +*/ +#define op_arithf_aux(L,v1,v2,fop) { \ + lua_Number n1; lua_Number n2; \ + if (tonumberns(v1, n1) && tonumberns(v2, n2)) { \ + pc++; setfltvalue(s2v(ra), fop(L, n1, n2)); \ + }} + + +/* +** Arithmetic operations over floats and others with register operands. +*/ +#define op_arithf(L,fop) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations with K operands for floats. +*/ +#define op_arithfK(L,fop) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); lua_assert(ttisnumber(v2)); \ + op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations over integers and floats. +*/ +#define op_arith_aux(L,v1,v2,iop,fop) { \ + StkId ra = RA(i); \ + if (ttisinteger(v1) && ttisinteger(v2)) { \ + lua_Integer i1 = ivalue(v1); lua_Integer i2 = ivalue(v2); \ + pc++; setivalue(s2v(ra), iop(L, i1, i2)); \ + } \ + else op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations with register operands. +*/ +#define op_arith(L,iop,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + op_arith_aux(L, v1, v2, iop, fop); } + + +/* +** Arithmetic operations with K operands. +*/ +#define op_arithK(L,iop,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); lua_assert(ttisnumber(v2)); \ + op_arith_aux(L, v1, v2, iop, fop); } + + +/* +** Bitwise operations with constant operand. +*/ +#define op_bitwiseK(L,op) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); \ + lua_Integer i1; \ + lua_Integer i2 = ivalue(v2); \ + if (tointegerns(v1, &i1)) { \ + pc++; setivalue(s2v(ra), op(i1, i2)); \ + }} + + +/* +** Bitwise operations with register operands. +*/ +#define op_bitwise(L,op) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + lua_Integer i1; lua_Integer i2; \ + if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) { \ + pc++; setivalue(s2v(ra), op(i1, i2)); \ + }} + + +/* +** Order operations with register operands. 'opn' actually works +** for all numbers, but the fast track improves performance for +** integers. +*/ +#define op_order(L,opi,opn,other) { \ + StkId ra = RA(i); \ + int cond; \ + TValue *rb = vRB(i); \ + if (ttisinteger(s2v(ra)) && ttisinteger(rb)) { \ + lua_Integer ia = ivalue(s2v(ra)); \ + lua_Integer ib = ivalue(rb); \ + cond = opi(ia, ib); \ + } \ + else if (ttisnumber(s2v(ra)) && ttisnumber(rb)) \ + cond = opn(s2v(ra), rb); \ + else \ + Protect(cond = other(L, s2v(ra), rb)); \ + docondjump(); } + + +/* +** Order operations with immediate operand. (Immediate operand is +** always small enough to have an exact representation as a float.) +*/ +#define op_orderI(L,opi,opf,inv,tm) { \ + StkId ra = RA(i); \ + int cond; \ + int im = GETARG_sB(i); \ + if (ttisinteger(s2v(ra))) \ + cond = opi(ivalue(s2v(ra)), im); \ + else if (ttisfloat(s2v(ra))) { \ + lua_Number fa = fltvalue(s2v(ra)); \ + lua_Number fim = cast_num(im); \ + cond = opf(fa, fim); \ + } \ + else { \ + int isf = GETARG_C(i); \ + Protect(cond = luaT_callorderiTM(L, s2v(ra), im, inv, isf, tm)); \ + } \ + docondjump(); } + +/* }================================================================== */ + + +/* +** {================================================================== +** Function 'luaV_execute': main interpreter loop +** =================================================================== +*/ + +/* +** some macros for common tasks in 'luaV_execute' +*/ #define RA(i) (base+GETARG_A(i)) -/* to be used after possible stack reallocation */ -#define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) -#define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) -#define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ - ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) -#define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ - ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) -#define KBx(i) \ - (k + (GETARG_Bx(i) != 0 ? GETARG_Bx(i) - 1 : GETARG_Ax(*ci->u.l.savedpc++))) - - -/* execute a jump instruction */ -#define dojump(ci,i,e) \ - { int a = GETARG_A(i); \ - if (a > 0) luaF_close(L, ci->u.l.base + a - 1); \ - ci->u.l.savedpc += GETARG_sBx(i) + e; } +#define RB(i) (base+GETARG_B(i)) +#define vRB(i) s2v(RB(i)) +#define KB(i) (k+GETARG_B(i)) +#define RC(i) (base+GETARG_C(i)) +#define vRC(i) s2v(RC(i)) +#define KC(i) (k+GETARG_C(i)) +#define RKC(i) ((TESTARG_k(i)) ? k + GETARG_C(i) : s2v(base + GETARG_C(i))) + + + +#define updatetrap(ci) (trap = ci->u.l.trap) + +#define updatebase(ci) (base = ci->func.p + 1) + + +#define updatestack(ci) \ + { if (l_unlikely(trap)) { updatebase(ci); ra = RA(i); } } + + +/* +** Execute a jump instruction. The 'updatetrap' allows signals to stop +** tight loops. (Without it, the local copy of 'trap' could never change.) +*/ +#define dojump(ci,i,e) { pc += GETARG_sJ(i) + e; updatetrap(ci); } + /* for test instructions, execute the jump instruction that follows it */ -#define donextjump(ci) { i = *ci->u.l.savedpc; dojump(ci, i, 1); } +#define donextjump(ci) { Instruction ni = *pc; dojump(ci, ni, 1); } +/* +** do a conditional jump: skip next instruction if 'cond' is not what +** was expected (parameter 'k'), else do next instruction, which must +** be a jump. +*/ +#define docondjump() if (cond != GETARG_k(i)) pc++; else donextjump(ci); -#define Protect(x) { {x;}; base = ci->u.l.base; } -#define checkGC(L,c) \ - Protect( luaC_condGC(L,{L->top = (c); /* limit of live values */ \ - luaC_step(L); \ - L->top = ci->top;}) /* restore top */ \ - luai_threadyield(L); ) +/* +** Correct global 'pc'. +*/ +#define savepc(L) (ci->u.l.savedpc = pc) +/* +** Whenever code can raise errors, the global 'pc' and the global +** 'top' must be correct to report occasional errors. +*/ +#define savestate(L,ci) (savepc(L), L->top.p = ci->top.p) + + +/* +** Protect code that, in general, can raise errors, reallocate the +** stack, and change the hooks. +*/ +#define Protect(exp) (savestate(L,ci), (exp), updatetrap(ci)) + +/* special version that does not change the top */ +#define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci)) + +/* +** Protect code that can only raise errors. (That is, it cannot change +** the stack or hooks.) +*/ +#define halfProtect(exp) (savestate(L,ci), (exp)) + +/* 'c' is the limit of live values in the stack */ +#define checkGC(L,c) \ + { luaC_condGC(L, (savepc(L), L->top.p = (c)), \ + updatetrap(ci)); \ + luai_threadyield(L); } + + +/* fetch an instruction and prepare its execution */ +#define vmfetch() { \ + if (l_unlikely(trap)) { /* stack reallocation or hooks? */ \ + trap = luaG_traceexec(L, pc); /* handle hooks */ \ + updatebase(ci); /* correct stack */ \ + } \ + i = *(pc++); \ +} + #define vmdispatch(o) switch(o) #define vmcase(l) case l: #define vmbreak break -void luaV_execute (lua_State *L) { - CallInfo *ci = L->ci; + +void luaV_execute (lua_State *L, CallInfo *ci) { LClosure *cl; TValue *k; StkId base; - newframe: /* reentry point when frame changes (call/return) */ - lua_assert(ci == L->ci); - cl = clLvalue(ci->func); + const Instruction *pc; + int trap; +#if LUA_USE_JUMPTABLE +#include "ljumptab.h" +#endif + startfunc: + trap = L->hookmask; + returning: /* trap already set */ + cl = ci_func(ci); k = cl->p->k; - base = ci->u.l.base; + pc = ci->u.l.savedpc; + if (l_unlikely(trap)) + trap = luaG_tracecall(L); + base = ci->func.p + 1; /* main loop of interpreter */ for (;;) { - Instruction i = *(ci->u.l.savedpc++); - StkId ra; - if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && - (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { - Protect(luaG_traceexec(L)); - } - /* WARNING: several calls may realloc the stack and invalidate 'ra' */ - ra = RA(i); - lua_assert(base == ci->u.l.base); - lua_assert(base <= L->top && L->top < L->stack + L->stacksize); + Instruction i; /* instruction being executed */ + vmfetch(); + #if 0 + /* low-level line tracing for debugging Lua */ + printf("line: %d\n", luaG_getfuncline(cl->p, pcRel(pc, cl->p))); + #endif + lua_assert(base == ci->func.p + 1); + lua_assert(base <= L->top.p && L->top.p <= L->stack_last.p); + /* invalidate top for instructions not expecting it */ + lua_assert(isIT(i) || (cast_void(L->top.p = base), 1)); vmdispatch (GET_OPCODE(i)) { vmcase(OP_MOVE) { + StkId ra = RA(i); setobjs2s(L, ra, RB(i)); vmbreak; } + vmcase(OP_LOADI) { + StkId ra = RA(i); + lua_Integer b = GETARG_sBx(i); + setivalue(s2v(ra), b); + vmbreak; + } + vmcase(OP_LOADF) { + StkId ra = RA(i); + int b = GETARG_sBx(i); + setfltvalue(s2v(ra), cast_num(b)); + vmbreak; + } vmcase(OP_LOADK) { + StkId ra = RA(i); TValue *rb = k + GETARG_Bx(i); setobj2s(L, ra, rb); vmbreak; } vmcase(OP_LOADKX) { + StkId ra = RA(i); TValue *rb; - lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); - rb = k + GETARG_Ax(*ci->u.l.savedpc++); + rb = k + GETARG_Ax(*pc); pc++; setobj2s(L, ra, rb); vmbreak; } - vmcase(OP_LOADBOOL) { - setbvalue(ra, GETARG_B(i)); - if (GETARG_C(i)) ci->u.l.savedpc++; /* skip next instruction (if C) */ + vmcase(OP_LOADFALSE) { + StkId ra = RA(i); + setbfvalue(s2v(ra)); + vmbreak; + } + vmcase(OP_LFALSESKIP) { + StkId ra = RA(i); + setbfvalue(s2v(ra)); + pc++; /* skip next instruction */ + vmbreak; + } + vmcase(OP_LOADTRUE) { + StkId ra = RA(i); + setbtvalue(s2v(ra)); vmbreak; } vmcase(OP_LOADNIL) { + StkId ra = RA(i); int b = GETARG_B(i); do { - setnilvalue(ra++); + setnilvalue(s2v(ra++)); } while (b--); vmbreak; } vmcase(OP_GETUPVAL) { + StkId ra = RA(i); int b = GETARG_B(i); - setobj2s(L, ra, cl->upvals[b]->v); + setobj2s(L, ra, cl->upvals[b]->v.p); + vmbreak; + } + vmcase(OP_SETUPVAL) { + StkId ra = RA(i); + UpVal *uv = cl->upvals[GETARG_B(i)]; + setobj(L, uv->v.p, s2v(ra)); + luaC_barrier(L, uv, s2v(ra)); vmbreak; } vmcase(OP_GETTABUP) { - int b = GETARG_B(i); - Protect(luaV_gettable(L, cl->upvals[b]->v, RKC(i), ra)); + StkId ra = RA(i); + const TValue *slot; + TValue *upval = cl->upvals[GETARG_B(i)]->v.p; + TValue *rc = KC(i); + TString *key = tsvalue(rc); /* key must be a short string */ + if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, upval, rc, ra, slot)); vmbreak; } vmcase(OP_GETTABLE) { - Protect(luaV_gettable(L, RB(i), RKC(i), ra)); + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = vRC(i); + lua_Unsigned n; + if (ttisinteger(rc) /* fast track for integers? */ + ? (cast_void(n = ivalue(rc)), luaV_fastgeti(L, rb, n, slot)) + : luaV_fastget(L, rb, rc, slot, luaH_get)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); vmbreak; } - vmcase(OP_SETTABUP) { - int a = GETARG_A(i); - Protect(luaV_settable(L, cl->upvals[a]->v, RKB(i), RKC(i))); + vmcase(OP_GETI) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + int c = GETARG_C(i); + if (luaV_fastgeti(L, rb, c, slot)) { + setobj2s(L, ra, slot); + } + else { + TValue key; + setivalue(&key, c); + Protect(luaV_finishget(L, rb, &key, ra, slot)); + } vmbreak; } - vmcase(OP_SETUPVAL) { - UpVal *uv = cl->upvals[GETARG_B(i)]; - setobj(L, uv->v, ra); - luaC_upvalbarrier(L, uv); + vmcase(OP_GETFIELD) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = KC(i); + TString *key = tsvalue(rc); /* key must be a short string */ + if (luaV_fastget(L, rb, key, slot, luaH_getshortstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); + vmbreak; + } + vmcase(OP_SETTABUP) { + const TValue *slot; + TValue *upval = cl->upvals[GETARG_A(i)]->v.p; + TValue *rb = KB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rb); /* key must be a short string */ + if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { + luaV_finishfastset(L, upval, slot, rc); + } + else + Protect(luaV_finishset(L, upval, rb, rc, slot)); vmbreak; } vmcase(OP_SETTABLE) { - Protect(luaV_settable(L, ra, RKB(i), RKC(i))); + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); /* key (table is in 'ra') */ + TValue *rc = RKC(i); /* value */ + lua_Unsigned n; + if (ttisinteger(rb) /* fast track for integers? */ + ? (cast_void(n = ivalue(rb)), luaV_fastgeti(L, s2v(ra), n, slot)) + : luaV_fastget(L, s2v(ra), rb, slot, luaH_get)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else + Protect(luaV_finishset(L, s2v(ra), rb, rc, slot)); + vmbreak; + } + vmcase(OP_SETI) { + StkId ra = RA(i); + const TValue *slot; + int c = GETARG_B(i); + TValue *rc = RKC(i); + if (luaV_fastgeti(L, s2v(ra), c, slot)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else { + TValue key; + setivalue(&key, c); + Protect(luaV_finishset(L, s2v(ra), &key, rc, slot)); + } + vmbreak; + } + vmcase(OP_SETFIELD) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = KB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rb); /* key must be a short string */ + if (luaV_fastget(L, s2v(ra), key, slot, luaH_getshortstr)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else + Protect(luaV_finishset(L, s2v(ra), rb, rc, slot)); vmbreak; } vmcase(OP_NEWTABLE) { - int b = GETARG_B(i); - int c = GETARG_C(i); - Table *t = luaH_new(L); - sethvalue(L, ra, t); + StkId ra = RA(i); + int b = GETARG_B(i); /* log2(hash size) + 1 */ + int c = GETARG_C(i); /* array size */ + Table *t; + if (b > 0) + b = 1 << (b - 1); /* size is 2^(b - 1) */ + lua_assert((!TESTARG_k(i)) == (GETARG_Ax(*pc) == 0)); + if (TESTARG_k(i)) /* non-zero extra argument? */ + c += GETARG_Ax(*pc) * (MAXARG_C + 1); /* add it to size */ + pc++; /* skip extra argument */ + L->top.p = ra + 1; /* correct top in case of emergency GC */ + t = luaH_new(L); /* memory allocation */ + sethvalue2s(L, ra, t); if (b != 0 || c != 0) - luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c)); + luaH_resize(L, t, c, b); /* idem */ checkGC(L, ra + 1); vmbreak; } vmcase(OP_SELF) { - StkId rb = RB(i); - setobjs2s(L, ra+1, rb); - Protect(luaV_gettable(L, rb, RKC(i), ra)); + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rc); /* key must be a string */ + setobj2s(L, ra + 1, rb); + if (luaV_fastget(L, rb, key, slot, luaH_getstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); vmbreak; } - vmcase(OP_ADD) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Number nb; lua_Number nc; - if (ttisinteger(rb) && ttisinteger(rc)) { - lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); - setivalue(ra, intop(+, ib, ic)); + vmcase(OP_ADDI) { + op_arithI(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_ADDK) { + op_arithK(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_SUBK) { + op_arithK(L, l_subi, luai_numsub); + vmbreak; + } + vmcase(OP_MULK) { + op_arithK(L, l_muli, luai_nummul); + vmbreak; + } + vmcase(OP_MODK) { + savestate(L, ci); /* in case of division by 0 */ + op_arithK(L, luaV_mod, luaV_modf); + vmbreak; + } + vmcase(OP_POWK) { + op_arithfK(L, luai_numpow); + vmbreak; + } + vmcase(OP_DIVK) { + op_arithfK(L, luai_numdiv); + vmbreak; + } + vmcase(OP_IDIVK) { + savestate(L, ci); /* in case of division by 0 */ + op_arithK(L, luaV_idiv, luai_numidiv); + vmbreak; + } + vmcase(OP_BANDK) { + op_bitwiseK(L, l_band); + vmbreak; + } + vmcase(OP_BORK) { + op_bitwiseK(L, l_bor); + vmbreak; + } + vmcase(OP_BXORK) { + op_bitwiseK(L, l_bxor); + vmbreak; + } + vmcase(OP_SHRI) { + StkId ra = RA(i); + TValue *rb = vRB(i); + int ic = GETARG_sC(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + pc++; setivalue(s2v(ra), luaV_shiftl(ib, -ic)); } - else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { - setfltvalue(ra, luai_numadd(L, nb, nc)); + vmbreak; + } + vmcase(OP_SHLI) { + StkId ra = RA(i); + TValue *rb = vRB(i); + int ic = GETARG_sC(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + pc++; setivalue(s2v(ra), luaV_shiftl(ic, ib)); } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_ADD)); } + vmbreak; + } + vmcase(OP_ADD) { + op_arith(L, l_addi, luai_numadd); vmbreak; } vmcase(OP_SUB) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Number nb; lua_Number nc; - if (ttisinteger(rb) && ttisinteger(rc)) { - lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); - setivalue(ra, intop(-, ib, ic)); - } - else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { - setfltvalue(ra, luai_numsub(L, nb, nc)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SUB)); } + op_arith(L, l_subi, luai_numsub); vmbreak; } vmcase(OP_MUL) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Number nb; lua_Number nc; - if (ttisinteger(rb) && ttisinteger(rc)) { - lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); - setivalue(ra, intop(*, ib, ic)); - } - else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { - setfltvalue(ra, luai_nummul(L, nb, nc)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_MUL)); } + op_arith(L, l_muli, luai_nummul); + vmbreak; + } + vmcase(OP_MOD) { + savestate(L, ci); /* in case of division by 0 */ + op_arith(L, luaV_mod, luaV_modf); + vmbreak; + } + vmcase(OP_POW) { + op_arithf(L, luai_numpow); vmbreak; } vmcase(OP_DIV) { /* float division (always with floats) */ - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Number nb; lua_Number nc; - if (tonumber(rb, &nb) && tonumber(rc, &nc)) { - setfltvalue(ra, luai_numdiv(L, nb, nc)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_DIV)); } + op_arithf(L, luai_numdiv); + vmbreak; + } + vmcase(OP_IDIV) { /* floor division */ + savestate(L, ci); /* in case of division by 0 */ + op_arith(L, luaV_idiv, luai_numidiv); vmbreak; } vmcase(OP_BAND) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Integer ib; lua_Integer ic; - if (tointeger(rb, &ib) && tointeger(rc, &ic)) { - setivalue(ra, intop(&, ib, ic)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BAND)); } + op_bitwise(L, l_band); vmbreak; } vmcase(OP_BOR) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Integer ib; lua_Integer ic; - if (tointeger(rb, &ib) && tointeger(rc, &ic)) { - setivalue(ra, intop(|, ib, ic)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BOR)); } + op_bitwise(L, l_bor); vmbreak; } vmcase(OP_BXOR) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Integer ib; lua_Integer ic; - if (tointeger(rb, &ib) && tointeger(rc, &ic)) { - setivalue(ra, intop(^, ib, ic)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BXOR)); } + op_bitwise(L, l_bxor); vmbreak; } - vmcase(OP_SHL) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Integer ib; lua_Integer ic; - if (tointeger(rb, &ib) && tointeger(rc, &ic)) { - setivalue(ra, luaV_shiftl(ib, ic)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHL)); } + vmcase(OP_SHR) { + op_bitwise(L, luaV_shiftr); vmbreak; } - vmcase(OP_SHR) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Integer ib; lua_Integer ic; - if (tointeger(rb, &ib) && tointeger(rc, &ic)) { - setivalue(ra, luaV_shiftl(ib, -ic)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SHR)); } + vmcase(OP_SHL) { + op_bitwise(L, luaV_shiftl); vmbreak; } - vmcase(OP_MOD) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Number nb; lua_Number nc; - if (ttisinteger(rb) && ttisinteger(rc)) { - lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); - setivalue(ra, luaV_mod(L, ib, ic)); - } - else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { - lua_Number m; - luai_nummod(L, nb, nc, m); - setfltvalue(ra, m); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_MOD)); } + vmcase(OP_MMBIN) { + StkId ra = RA(i); + Instruction pi = *(pc - 2); /* original arith. expression */ + TValue *rb = vRB(i); + TMS tm = (TMS)GETARG_C(i); + StkId result = RA(pi); + lua_assert(OP_ADD <= GET_OPCODE(pi) && GET_OPCODE(pi) <= OP_SHR); + Protect(luaT_trybinTM(L, s2v(ra), rb, result, tm)); vmbreak; } - vmcase(OP_IDIV) { /* floor division */ - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Number nb; lua_Number nc; - if (ttisinteger(rb) && ttisinteger(rc)) { - lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc); - setivalue(ra, luaV_div(L, ib, ic)); - } - else if (tonumber(rb, &nb) && tonumber(rc, &nc)) { - setfltvalue(ra, luai_numidiv(L, nb, nc)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_IDIV)); } + vmcase(OP_MMBINI) { + StkId ra = RA(i); + Instruction pi = *(pc - 2); /* original arith. expression */ + int imm = GETARG_sB(i); + TMS tm = (TMS)GETARG_C(i); + int flip = GETARG_k(i); + StkId result = RA(pi); + Protect(luaT_trybiniTM(L, s2v(ra), imm, flip, result, tm)); vmbreak; } - vmcase(OP_POW) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - lua_Number nb; lua_Number nc; - if (tonumber(rb, &nb) && tonumber(rc, &nc)) { - setfltvalue(ra, luai_numpow(L, nb, nc)); - } - else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_POW)); } + vmcase(OP_MMBINK) { + StkId ra = RA(i); + Instruction pi = *(pc - 2); /* original arith. expression */ + TValue *imm = KB(i); + TMS tm = (TMS)GETARG_C(i); + int flip = GETARG_k(i); + StkId result = RA(pi); + Protect(luaT_trybinassocTM(L, s2v(ra), imm, flip, result, tm)); vmbreak; } vmcase(OP_UNM) { - TValue *rb = RB(i); + StkId ra = RA(i); + TValue *rb = vRB(i); lua_Number nb; if (ttisinteger(rb)) { lua_Integer ib = ivalue(rb); - setivalue(ra, intop(-, 0, ib)); + setivalue(s2v(ra), intop(-, 0, ib)); } - else if (tonumber(rb, &nb)) { - setfltvalue(ra, luai_numunm(L, nb)); + else if (tonumberns(rb, nb)) { + setfltvalue(s2v(ra), luai_numunm(L, nb)); } - else { + else Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM)); - } vmbreak; } vmcase(OP_BNOT) { - TValue *rb = RB(i); + StkId ra = RA(i); + TValue *rb = vRB(i); lua_Integer ib; - if (tointeger(rb, &ib)) { - setivalue(ra, intop(^, ~l_castS2U(0), ib)); + if (tointegerns(rb, &ib)) { + setivalue(s2v(ra), intop(^, ~l_castS2U(0), ib)); } - else { + else Protect(luaT_trybinTM(L, rb, rb, ra, TM_BNOT)); - } vmbreak; } vmcase(OP_NOT) { - TValue *rb = RB(i); - int res = l_isfalse(rb); /* next assignment may change this value */ - setbvalue(ra, res); + StkId ra = RA(i); + TValue *rb = vRB(i); + if (l_isfalse(rb)) + setbtvalue(s2v(ra)); + else + setbfvalue(s2v(ra)); vmbreak; } vmcase(OP_LEN) { - Protect(luaV_objlen(L, ra, RB(i))); + StkId ra = RA(i); + Protect(luaV_objlen(L, ra, vRB(i))); vmbreak; } vmcase(OP_CONCAT) { - int b = GETARG_B(i); - int c = GETARG_C(i); - StkId rb; - L->top = base + c + 1; /* mark the end of concat operands */ - Protect(luaV_concat(L, c - b + 1)); - ra = RA(i); /* 'luav_concat' may invoke TMs and move the stack */ - rb = base + b; - setobjs2s(L, ra, rb); - checkGC(L, (ra >= rb ? ra + 1 : rb)); - L->top = ci->top; /* restore top */ + StkId ra = RA(i); + int n = GETARG_B(i); /* number of elements to concatenate */ + L->top.p = ra + n; /* mark the end of concat operands */ + ProtectNT(luaV_concat(L, n)); + checkGC(L, L->top.p); /* 'luaV_concat' ensures correct top */ + vmbreak; + } + vmcase(OP_CLOSE) { + StkId ra = RA(i); + Protect(luaF_close(L, ra, LUA_OK, 1)); + vmbreak; + } + vmcase(OP_TBC) { + StkId ra = RA(i); + /* create new to-be-closed upvalue */ + halfProtect(luaF_newtbcupval(L, ra)); vmbreak; } vmcase(OP_JMP) { @@ -1032,234 +1601,291 @@ void luaV_execute (lua_State *L) { vmbreak; } vmcase(OP_EQ) { - TValue *rb = RKB(i); - TValue *rc = RKC(i); - Protect( - if (cast_int(luaV_equalobj(L, rb, rc)) != GETARG_A(i)) - ci->u.l.savedpc++; - else - donextjump(ci); - ) + StkId ra = RA(i); + int cond; + TValue *rb = vRB(i); + Protect(cond = luaV_equalobj(L, s2v(ra), rb)); + docondjump(); vmbreak; } vmcase(OP_LT) { - Protect( - if (luaV_lessthan(L, RKB(i), RKC(i)) != GETARG_A(i)) - ci->u.l.savedpc++; - else - donextjump(ci); - ) + op_order(L, l_lti, LTnum, lessthanothers); vmbreak; } vmcase(OP_LE) { - Protect( - if (luaV_lessequal(L, RKB(i), RKC(i)) != GETARG_A(i)) - ci->u.l.savedpc++; - else - donextjump(ci); - ) + op_order(L, l_lei, LEnum, lessequalothers); + vmbreak; + } + vmcase(OP_EQK) { + StkId ra = RA(i); + TValue *rb = KB(i); + /* basic types do not use '__eq'; we can use raw equality */ + int cond = luaV_rawequalobj(s2v(ra), rb); + docondjump(); + vmbreak; + } + vmcase(OP_EQI) { + StkId ra = RA(i); + int cond; + int im = GETARG_sB(i); + if (ttisinteger(s2v(ra))) + cond = (ivalue(s2v(ra)) == im); + else if (ttisfloat(s2v(ra))) + cond = luai_numeq(fltvalue(s2v(ra)), cast_num(im)); + else + cond = 0; /* other types cannot be equal to a number */ + docondjump(); + vmbreak; + } + vmcase(OP_LTI) { + op_orderI(L, l_lti, luai_numlt, 0, TM_LT); + vmbreak; + } + vmcase(OP_LEI) { + op_orderI(L, l_lei, luai_numle, 0, TM_LE); + vmbreak; + } + vmcase(OP_GTI) { + op_orderI(L, l_gti, luai_numgt, 1, TM_LT); + vmbreak; + } + vmcase(OP_GEI) { + op_orderI(L, l_gei, luai_numge, 1, TM_LE); vmbreak; } vmcase(OP_TEST) { - if (GETARG_C(i) ? l_isfalse(ra) : !l_isfalse(ra)) - ci->u.l.savedpc++; - else - donextjump(ci); + StkId ra = RA(i); + int cond = !l_isfalse(s2v(ra)); + docondjump(); vmbreak; } vmcase(OP_TESTSET) { - TValue *rb = RB(i); - if (GETARG_C(i) ? l_isfalse(rb) : !l_isfalse(rb)) - ci->u.l.savedpc++; + StkId ra = RA(i); + TValue *rb = vRB(i); + if (l_isfalse(rb) == GETARG_k(i)) + pc++; else { - setobjs2s(L, ra, rb); + setobj2s(L, ra, rb); donextjump(ci); } vmbreak; } vmcase(OP_CALL) { + StkId ra = RA(i); + CallInfo *newci; int b = GETARG_B(i); int nresults = GETARG_C(i) - 1; - if (b != 0) L->top = ra+b; /* else previous instruction set top */ - if (luaD_precall(L, ra, nresults)) { /* C function? */ - if (nresults >= 0) L->top = ci->top; /* adjust results */ - base = ci->u.l.base; - } - else { /* Lua function */ - ci = L->ci; - ci->callstatus |= CIST_REENTRY; - goto newframe; /* restart luaV_execute over new Lua function */ + if (b != 0) /* fixed number of arguments? */ + L->top.p = ra + b; /* top signals number of arguments */ + /* else previous instruction set top */ + savepc(L); /* in case of errors */ + if ((newci = luaD_precall(L, ra, nresults)) == NULL) + updatetrap(ci); /* C call; nothing else to be done */ + else { /* Lua call: run function in this same C frame */ + ci = newci; + goto startfunc; } vmbreak; } vmcase(OP_TAILCALL) { - int b = GETARG_B(i); - if (b != 0) L->top = ra+b; /* else previous instruction set top */ - lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); - if (luaD_precall(L, ra, LUA_MULTRET)) /* C function? */ - base = ci->u.l.base; - else { - /* tail call: put called frame (n) in place of caller one (o) */ - CallInfo *nci = L->ci; /* called frame */ - CallInfo *oci = nci->previous; /* caller frame */ - StkId nfunc = nci->func; /* called function */ - StkId ofunc = oci->func; /* caller function */ - /* last stack slot filled by 'precall' */ - StkId lim = nci->u.l.base + getproto(nfunc)->numparams; - int aux; - /* close all upvalues from previous call */ - if (cl->p->sizep > 0) luaF_close(L, oci->u.l.base); - /* move new frame into old one */ - for (aux = 0; nfunc + aux < lim; aux++) - setobjs2s(L, ofunc + aux, nfunc + aux); - oci->u.l.base = ofunc + (nci->u.l.base - nfunc); /* correct base */ - oci->top = L->top = ofunc + (L->top - nfunc); /* correct top */ - oci->u.l.savedpc = nci->u.l.savedpc; - oci->callstatus |= CIST_TAIL; /* function was tail called */ - ci = L->ci = oci; /* remove new frame */ - lua_assert(L->top == oci->u.l.base + getproto(ofunc)->maxstacksize); - goto newframe; /* restart luaV_execute over new Lua function */ + StkId ra = RA(i); + int b = GETARG_B(i); /* number of arguments + 1 (function) */ + int n; /* number of results when calling a C function */ + int nparams1 = GETARG_C(i); + /* delta is virtual 'func' - real 'func' (vararg functions) */ + int delta = (nparams1) ? ci->u.l.nextraargs + nparams1 : 0; + if (b != 0) + L->top.p = ra + b; + else /* previous instruction set top */ + b = cast_int(L->top.p - ra); + savepc(ci); /* several calls here can raise errors */ + if (TESTARG_k(i)) { + luaF_closeupval(L, base); /* close upvalues from current call */ + lua_assert(L->tbclist.p < base); /* no pending tbc variables */ + lua_assert(base == ci->func.p + 1); + } + if ((n = luaD_pretailcall(L, ci, ra, b, delta)) < 0) /* Lua function? */ + goto startfunc; /* execute the callee */ + else { /* C function? */ + ci->func.p -= delta; /* restore 'func' (if vararg) */ + luaD_poscall(L, ci, n); /* finish caller */ + updatetrap(ci); /* 'luaD_poscall' can change hooks */ + goto ret; /* caller returns after the tail call */ } - vmbreak; } vmcase(OP_RETURN) { - int b = GETARG_B(i); - if (cl->p->sizep > 0) luaF_close(L, base); - b = luaD_poscall(L, ra, (b != 0 ? b - 1 : L->top - ra)); - if (!(ci->callstatus & CIST_REENTRY)) /* 'ci' still the called one */ - return; /* external invocation: return */ - else { /* invocation via reentry: continue execution */ - ci = L->ci; - if (b) L->top = ci->top; - lua_assert(isLua(ci)); - lua_assert(GET_OPCODE(*((ci)->u.l.savedpc - 1)) == OP_CALL); - goto newframe; /* restart luaV_execute over new Lua function */ + StkId ra = RA(i); + int n = GETARG_B(i) - 1; /* number of results */ + int nparams1 = GETARG_C(i); + if (n < 0) /* not fixed? */ + n = cast_int(L->top.p - ra); /* get what is available */ + savepc(ci); + if (TESTARG_k(i)) { /* may there be open upvalues? */ + ci->u2.nres = n; /* save number of returns */ + if (L->top.p < ci->top.p) + L->top.p = ci->top.p; + luaF_close(L, base, CLOSEKTOP, 1); + updatetrap(ci); + updatestack(ci); } + if (nparams1) /* vararg function? */ + ci->func.p -= ci->u.l.nextraargs + nparams1; + L->top.p = ra + n; /* set call for 'luaD_poscall' */ + luaD_poscall(L, ci, n); + updatetrap(ci); /* 'luaD_poscall' can change hooks */ + goto ret; } - vmcase(OP_FORLOOP) { - if (ttisinteger(ra)) { /* integer loop? */ - lua_Integer step = ivalue(ra + 2); - lua_Integer idx = ivalue(ra) + step; /* increment index */ - lua_Integer limit = ivalue(ra + 1); - if ((0 < step) ? (idx <= limit) : (limit <= idx)) { - ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ - chgivalue(ra, idx); /* update internal index... */ - setivalue(ra + 3, idx); /* ...and external index */ + vmcase(OP_RETURN0) { + if (l_unlikely(L->hookmask)) { + StkId ra = RA(i); + L->top.p = ra; + savepc(ci); + luaD_poscall(L, ci, 0); /* no hurry... */ + trap = 1; + } + else { /* do the 'poscall' here */ + int nres; + L->ci = ci->previous; /* back to caller */ + L->top.p = base - 1; + for (nres = ci->nresults; l_unlikely(nres > 0); nres--) + setnilvalue(s2v(L->top.p++)); /* all results are nil */ + } + goto ret; + } + vmcase(OP_RETURN1) { + if (l_unlikely(L->hookmask)) { + StkId ra = RA(i); + L->top.p = ra + 1; + savepc(ci); + luaD_poscall(L, ci, 1); /* no hurry... */ + trap = 1; + } + else { /* do the 'poscall' here */ + int nres = ci->nresults; + L->ci = ci->previous; /* back to caller */ + if (nres == 0) + L->top.p = base - 1; /* asked for no results */ + else { + StkId ra = RA(i); + setobjs2s(L, base - 1, ra); /* at least this result */ + L->top.p = base; + for (; l_unlikely(nres > 1); nres--) + setnilvalue(s2v(L->top.p++)); /* complete missing results */ } } - else { /* floating loop */ - lua_Number step = fltvalue(ra + 2); - lua_Number idx = luai_numadd(L, fltvalue(ra), step); /* inc. index */ - lua_Number limit = fltvalue(ra + 1); - if (luai_numlt(0, step) ? luai_numle(idx, limit) - : luai_numle(limit, idx)) { - ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ - chgfltvalue(ra, idx); /* update internal index... */ - setfltvalue(ra + 3, idx); /* ...and external index */ + ret: /* return from a Lua function */ + if (ci->callstatus & CIST_FRESH) + return; /* end this frame */ + else { + ci = ci->previous; + goto returning; /* continue running caller in this frame */ + } + } + vmcase(OP_FORLOOP) { + StkId ra = RA(i); + if (ttisinteger(s2v(ra + 2))) { /* integer loop? */ + lua_Unsigned count = l_castS2U(ivalue(s2v(ra + 1))); + if (count > 0) { /* still more iterations? */ + lua_Integer step = ivalue(s2v(ra + 2)); + lua_Integer idx = ivalue(s2v(ra)); /* internal index */ + chgivalue(s2v(ra + 1), count - 1); /* update counter */ + idx = intop(+, idx, step); /* add step to index */ + chgivalue(s2v(ra), idx); /* update internal index */ + setivalue(s2v(ra + 3), idx); /* and control variable */ + pc -= GETARG_Bx(i); /* jump back */ } } + else if (floatforloop(ra)) /* float loop */ + pc -= GETARG_Bx(i); /* jump back */ + updatetrap(ci); /* allows a signal to break the loop */ vmbreak; } vmcase(OP_FORPREP) { - TValue *init = ra; - TValue *plimit = ra + 1; - TValue *pstep = ra + 2; - lua_Integer ilimit; - int stopnow; - if (ttisinteger(init) && ttisinteger(pstep) && - forlimit(plimit, &ilimit, ivalue(pstep), &stopnow)) { - /* all values are integer */ - lua_Integer initv = (stopnow ? 0 : ivalue(init)); - setivalue(plimit, ilimit); - setivalue(init, initv - ivalue(pstep)); - } - else { /* try making all values floats */ - lua_Number ninit; lua_Number nlimit; lua_Number nstep; - if (!tonumber(plimit, &nlimit)) - luaG_runerror(L, "'for' limit must be a number"); - setfltvalue(plimit, nlimit); - if (!tonumber(pstep, &nstep)) - luaG_runerror(L, "'for' step must be a number"); - setfltvalue(pstep, nstep); - if (!tonumber(init, &ninit)) - luaG_runerror(L, "'for' initial value must be a number"); - setfltvalue(init, luai_numsub(L, ninit, nstep)); - } - ci->u.l.savedpc += GETARG_sBx(i); + StkId ra = RA(i); + savestate(L, ci); /* in case of errors */ + if (forprep(L, ra)) + pc += GETARG_Bx(i) + 1; /* skip the loop */ vmbreak; } + vmcase(OP_TFORPREP) { + StkId ra = RA(i); + /* create to-be-closed upvalue (if needed) */ + halfProtect(luaF_newtbcupval(L, ra + 3)); + pc += GETARG_Bx(i); + i = *(pc++); /* go to next instruction */ + lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i)); + goto l_tforcall; + } vmcase(OP_TFORCALL) { - StkId cb = ra + 3; /* call base */ - setobjs2s(L, cb+2, ra+2); - setobjs2s(L, cb+1, ra+1); - setobjs2s(L, cb, ra); - L->top = cb + 3; /* func. + 2 args (state and index) */ - Protect(luaD_call(L, cb, GETARG_C(i), 1)); - L->top = ci->top; - i = *(ci->u.l.savedpc++); /* go to next instruction */ - ra = RA(i); - lua_assert(GET_OPCODE(i) == OP_TFORLOOP); + l_tforcall: { + StkId ra = RA(i); + /* 'ra' has the iterator function, 'ra + 1' has the state, + 'ra + 2' has the control variable, and 'ra + 3' has the + to-be-closed variable. The call will use the stack after + these values (starting at 'ra + 4') + */ + /* push function, state, and control variable */ + memcpy(ra + 4, ra, 3 * sizeof(*ra)); + L->top.p = ra + 4 + 3; + ProtectNT(luaD_call(L, ra + 4, GETARG_C(i))); /* do the call */ + updatestack(ci); /* stack may have changed */ + i = *(pc++); /* go to next instruction */ + lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i)); goto l_tforloop; - } + }} vmcase(OP_TFORLOOP) { - l_tforloop: - if (!ttisnil(ra + 1)) { /* continue loop? */ - setobjs2s(L, ra, ra + 1); /* save control variable */ - ci->u.l.savedpc += GETARG_sBx(i); /* jump back */ + l_tforloop: { + StkId ra = RA(i); + if (!ttisnil(s2v(ra + 4))) { /* continue loop? */ + setobjs2s(L, ra + 2, ra + 4); /* save control variable */ + pc -= GETARG_Bx(i); /* jump back */ } vmbreak; - } + }} vmcase(OP_SETLIST) { + StkId ra = RA(i); int n = GETARG_B(i); - int c = GETARG_C(i); - unsigned int last; - Table *h; - if (n == 0) n = cast_int(L->top - ra) - 1; - if (c == 0) { - lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG); - c = GETARG_Ax(*ci->u.l.savedpc++); + unsigned int last = GETARG_C(i); + Table *h = hvalue(s2v(ra)); + if (n == 0) + n = cast_int(L->top.p - ra) - 1; /* get up to the top */ + else + L->top.p = ci->top.p; /* correct top in case of emergency GC */ + last += n; + if (TESTARG_k(i)) { + last += GETARG_Ax(*pc) * (MAXARG_C + 1); + pc++; } - luai_runtimecheck(L, ttistable(ra)); - h = hvalue(ra); - last = ((c-1)*LFIELDS_PER_FLUSH) + n; - if (last > h->sizearray) /* needs more space? */ - luaH_resizearray(L, h, last); /* pre-allocate it at once */ + if (last > luaH_realasize(h)) /* needs more space? */ + luaH_resizearray(L, h, last); /* preallocate it at once */ for (; n > 0; n--) { - TValue *val = ra+n; - luaH_setint(L, h, last--, val); - luaC_barrierback(L, h, val); + TValue *val = s2v(ra + n); + setobj2t(L, &h->array[last - 1], val); + last--; + luaC_barrierback(L, obj2gco(h), val); } - L->top = ci->top; /* correct top (in case of previous open call) */ vmbreak; } vmcase(OP_CLOSURE) { + StkId ra = RA(i); Proto *p = cl->p->p[GETARG_Bx(i)]; - LClosure *ncl = getcached(p, cl->upvals, base); /* cached closure */ - if (ncl == NULL) /* no match? */ - pushclosure(L, p, cl->upvals, base, ra); /* create a new one */ - else - setclLvalue(L, ra, ncl); /* push cashed closure */ + halfProtect(pushclosure(L, p, cl->upvals, base, ra)); checkGC(L, ra + 1); vmbreak; } vmcase(OP_VARARG) { - int b = GETARG_B(i) - 1; - int j; - int n = cast_int(base - ci->func) - cl->p->numparams - 1; - if (b < 0) { /* B == 0? */ - b = n; /* get all var. arguments */ - Protect(luaD_checkstack(L, n)); - ra = RA(i); /* previous call may change the stack */ - L->top = ra + n; - } - for (j = 0; j < b; j++) { - if (j < n) { - setobjs2s(L, ra + j, base - n + j); - } - else { - setnilvalue(ra + j); - } + StkId ra = RA(i); + int n = GETARG_C(i) - 1; /* required results */ + Protect(luaT_getvarargs(L, ci, ra, n)); + vmbreak; + } + vmcase(OP_VARARGPREP) { + ProtectNT(luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p)); + if (l_unlikely(trap)) { /* previous "Protect" updated trap */ + luaD_hookcall(L, ci); + L->oldpc = 1; /* next opcode will be seen as a "new" line */ } + updatebase(ci); /* function has new base after adjustment */ vmbreak; } vmcase(OP_EXTRAARG) { @@ -1271,4 +1897,3 @@ void luaV_execute (lua_State *L) { } /* }================================================================== */ - diff --git a/libs/lua/lvm.h b/libs/lua/lvm.h index 0613826a..dba1ad27 100644 --- a/libs/lua/lvm.h +++ b/libs/lua/lvm.h @@ -1,5 +1,5 @@ /* -** $Id: lvm.h,v 2.35 2015/02/20 14:27:53 roberto Exp $ +** $Id: lvm.h $ ** Lua virtual machine ** See Copyright Notice in lua.h */ @@ -33,35 +33,108 @@ ** integral values) */ #if !defined(LUA_FLOORN2I) -#define LUA_FLOORN2I 0 +#define LUA_FLOORN2I F2Ieq #endif +/* +** Rounding modes for float->integer coercion + */ +typedef enum { + F2Ieq, /* no rounding; accepts only integral values */ + F2Ifloor, /* takes the floor of the number */ + F2Iceil /* takes the ceil of the number */ +} F2Imod; + + +/* convert an object to a float (including string coercion) */ #define tonumber(o,n) \ (ttisfloat(o) ? (*(n) = fltvalue(o), 1) : luaV_tonumber_(o,n)) + +/* convert an object to a float (without string coercion) */ +#define tonumberns(o,n) \ + (ttisfloat(o) ? ((n) = fltvalue(o), 1) : \ + (ttisinteger(o) ? ((n) = cast_num(ivalue(o)), 1) : 0)) + + +/* convert an object to an integer (including string coercion) */ #define tointeger(o,i) \ - (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I)) + (l_likely(ttisinteger(o)) ? (*(i) = ivalue(o), 1) \ + : luaV_tointeger(o,i,LUA_FLOORN2I)) + + +/* convert an object to an integer (without string coercion) */ +#define tointegerns(o,i) \ + (l_likely(ttisinteger(o)) ? (*(i) = ivalue(o), 1) \ + : luaV_tointegerns(o,i,LUA_FLOORN2I)) + #define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2)) #define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) +/* +** fast track for 'gettable': if 't' is a table and 't[k]' is present, +** return 1 with 'slot' pointing to 't[k]' (position of final result). +** Otherwise, return 0 (meaning it will have to check metamethod) +** with 'slot' pointing to an empty 't[k]' (if 't' is a table) or NULL +** (otherwise). 'f' is the raw get function to use. +*/ +#define luaV_fastget(L,t,k,slot,f) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = f(hvalue(t), k), /* else, do raw access */ \ + !isempty(slot))) /* result not empty? */ + + +/* +** Special case of 'luaV_fastget' for integers, inlining the fast case +** of 'luaH_getint'. +*/ +#define luaV_fastgeti(L,t,k,slot) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = (l_castS2U(k) - 1u < hvalue(t)->alimit) \ + ? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \ + !isempty(slot))) /* result not empty? */ + + +/* +** Finish a fast set operation (when fast get succeeds). In that case, +** 'slot' points to the place to put the value. +*/ +#define luaV_finishfastset(L,t,slot,v) \ + { setobj2t(L, cast(TValue *,slot), v); \ + luaC_barrierback(L, gcvalue(t), v); } + + +/* +** Shift right is the same as shift left with a negative 'y' +*/ +#define luaV_shiftr(x,y) luaV_shiftl(x,intop(-, 0, y)) + + + LUAI_FUNC int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2); LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_tonumber_ (const TValue *obj, lua_Number *n); -LUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode); -LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, - StkId val); -LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, - StkId val); +LUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode); +LUAI_FUNC int luaV_tointegerns (const TValue *obj, lua_Integer *p, + F2Imod mode); +LUAI_FUNC int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode); +LUAI_FUNC void luaV_finishget (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); +LUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + TValue *val, const TValue *slot); LUAI_FUNC void luaV_finishOp (lua_State *L); -LUAI_FUNC void luaV_execute (lua_State *L); +LUAI_FUNC void luaV_execute (lua_State *L, CallInfo *ci); LUAI_FUNC void luaV_concat (lua_State *L, int total); -LUAI_FUNC lua_Integer luaV_div (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Integer luaV_idiv (lua_State *L, lua_Integer x, lua_Integer y); LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Number luaV_modf (lua_State *L, lua_Number x, lua_Number y); LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y); LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb); diff --git a/libs/lua/lzio.c b/libs/lua/lzio.c index 46493920..cd0a02d5 100644 --- a/libs/lua/lzio.c +++ b/libs/lua/lzio.c @@ -1,5 +1,5 @@ /* -** $Id: lzio.c,v 1.36 2014/11/02 19:19:04 roberto Exp $ +** $Id: lzio.c $ ** Buffered streams ** See Copyright Notice in lua.h */ @@ -66,13 +66,3 @@ size_t luaZ_read (ZIO *z, void *b, size_t n) { return 0; } -/* ------------------------------------------------------------------------ */ -char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { - if (n > buff->buffsize) { - if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; - luaZ_resizebuffer(L, buff, n); - } - return buff->buffer; -} - - diff --git a/libs/lua/lzio.h b/libs/lua/lzio.h index b2e56bcd..38f397fd 100644 --- a/libs/lua/lzio.h +++ b/libs/lua/lzio.h @@ -1,5 +1,5 @@ /* -** $Id: lzio.h,v 1.30 2014/12/19 17:26:14 roberto Exp $ +** $Id: lzio.h $ ** Buffered streams ** See Copyright Notice in lua.h */ @@ -44,7 +44,6 @@ typedef struct Mbuffer { #define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) -LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data); LUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n); /* read next n bytes */ diff --git a/src/astar.h b/src/astar.h new file mode 100644 index 00000000..0c5bc2df --- /dev/null +++ b/src/astar.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +template std::vector astar( + T start, T target, + std::vector>(*get_neighbors)(T), + float(*get_distance)(T, T)) { + + using queueItem = std::pair; + auto cmp = [](queueItem left, queueItem right) { return left.first > right.first; }; + std::priority_queue, decltype(cmp)> todo{cmp}; + + std::unordered_map cost_so_far; + std::unordered_map came_from; + cost_so_far[start] = 0; + + todo.push({0, start}); + while(!todo.empty()) { + auto [priority, position] = todo.top(); + todo.pop(); + if (position == target) + break; + for(auto [pos, c] : get_neighbors(position)) { + auto new_cost = cost_so_far[position] + c; + if (cost_so_far.find(pos) == cost_so_far.end() || new_cost < cost_so_far[pos]) { + cost_so_far[pos] = new_cost; + priority = new_cost + get_distance(pos, target); + todo.push({priority, pos}); + came_from[pos] = position; + } + } + } + + std::vector path; + while(true) { + if (came_from.find(target) == came_from.end()) { + std::reverse(path.begin(), path.end()); + return path; + } + path.push_back(target); + target = came_from[target]; + } +} diff --git a/src/collisionable.cpp b/src/collisionable.cpp deleted file mode 100644 index 1b87f868..00000000 --- a/src/collisionable.cpp +++ /dev/null @@ -1,420 +0,0 @@ -#include -#include -#include "collisionable.h" -#include "Renderable.h" -#include "vectorUtils.h" -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsuggest-override" -#endif//__GNUC__ -#include "Box2D/Box2D.h" -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif//__GNUC__ - - -#define BOX2D_SCALE 20.0f -static inline glm::vec2 b2v(b2Vec2 v) -{ - return glm::vec2(v.x * BOX2D_SCALE, v.y * BOX2D_SCALE); -} -static inline b2Vec2 v2b(glm::vec2 v) -{ - return b2Vec2(v.x / BOX2D_SCALE, v.y / BOX2D_SCALE); -} - -b2World* CollisionManager::world; - -void CollisionManager::initialize() -{ - world = new b2World(b2Vec2(0, 0)); -} - -class QueryCallback : public b2QueryCallback -{ -public: - PVector list; - - /// Called for each fixture found in the query AABB. - /// @return false to terminate the query. - virtual bool ReportFixture(b2Fixture* fixture) override - { - P ptr = (Collisionable*)fixture->GetBody()->GetUserData(); - if (ptr) - list.push_back(ptr); - return true; - } -}; - -PVector CollisionManager::queryArea(glm::vec2 lowerBound, glm::vec2 upperBound) -{ - QueryCallback callback; - b2AABB aabb; - aabb.lowerBound = v2b(lowerBound); - aabb.upperBound = v2b(upperBound); - if (aabb.lowerBound.x > aabb.upperBound.x) - std::swap(aabb.upperBound.x, aabb.lowerBound.x); - if (aabb.lowerBound.y > aabb.upperBound.y) - std::swap(aabb.upperBound.y, aabb.lowerBound.y); - world->QueryAABB(&callback, aabb); - return callback.list; -} - -class Collision -{ -public: - P A; - P B; - float collision_force; - - Collision(P A, P B, float collision_force) - : A(A), B(B), collision_force(collision_force) - {} -}; - -void CollisionManager::handleCollisions(float delta) -{ - if (delta <= 0.0f) - return; - - Collisionable* destroy = NULL; - world->Step(delta, 4, 8); - std::vector collisions; - for(b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext()) - { - if (contact->IsTouching() && contact->IsEnabled()) - { - Collisionable* A = (Collisionable*)contact->GetFixtureA()->GetBody()->GetUserData(); - Collisionable* B = (Collisionable*)contact->GetFixtureB()->GetBody()->GetUserData(); - if (!A->isDestroyed() && !B->isDestroyed()) - { - float collision_force = 0.0f; - for (int n = 0; n < contact->GetManifold()->pointCount; n++) - { - collision_force += contact->GetManifold()->points[n].normalImpulse * BOX2D_SCALE; - } - collisions.push_back(Collision(A, B, collision_force)); - }else{ - if (A->isDestroyed()) - { - contact->GetFixtureA()->SetSensor(true); - destroy = A; - } - if (B->isDestroyed()) - { - contact->GetFixtureB()->SetSensor(true); - destroy = B; - } - } - } - } - - //Lazy cleanup of already destroyed bodies. We cannot destroy the bodies while we are walking trough the ContactList, as it would invalidate the contact we are iterating on. - if (destroy && destroy->body) - { - world->DestroyBody(destroy->body); - destroy->body = NULL; - } - - for(unsigned int n=0; ncollide(B, collisions[n].collision_force); - B->collide(A, collisions[n].collision_force); - } - } -} - -Collisionable::Collisionable(float radius) -{ - enable_physics = false; - static_physics = false; - body = NULL; - multiplayer_replication_object_significant_range = -1; - - setCollisionRadius(radius); -} - -Collisionable::Collisionable(glm::vec2 box_size, glm::vec2 box_origin) -{ - enable_physics = false; - static_physics = false; - body = NULL; - - setCollisionBox(box_size, box_origin); -} - -Collisionable::Collisionable(const std::vector& shape) -{ - enable_physics = false; - static_physics = false; - body = NULL; - - setCollisionShape(shape); -} - -Collisionable::~Collisionable() -{ - destroyBody(); -} - -void Collisionable::setCollisionRadius(float radius) -{ - if (radius <= 0) - { - destroyBody(); - }else{ - b2CircleShape shape; - shape.m_radius = radius / BOX2D_SCALE; - - createBody(&shape); - } -} - -void Collisionable::setCollisionBox(glm::vec2 box_size, glm::vec2 box_origin) -{ - b2PolygonShape shape; - shape.SetAsBox(box_size.x / 2.f / BOX2D_SCALE, box_size.y / 2.f / BOX2D_SCALE, v2b(box_origin), 0); - - createBody(&shape); -} - -void Collisionable::setCollisionShape(const std::vector& shapeList) -{ - for(unsigned int offset=1; offset shapeList.size() - offset + 1) - len = shapeList.size() - offset + 1; - if (len < 3) - break; - - b2Vec2 points[b2_maxPolygonVertices]; - points[0] = v2b(shapeList[0]); - for(size_t n=0; n(len)); - if (!valid) - { - shape.SetAsBox(1.f/BOX2D_SCALE, 1.f/BOX2D_SCALE, points[0], 0); - LOG(ERROR) << "Failed to set valid collision shape: " << shapeList.size(); - for(size_t n=0; nCreateFixture(&shapeDef); - } - } -} - -void Collisionable::setCollisionChain(const std::vector& points, bool loop) -{ - b2ChainShape shape; - std::vector b_points; - b_points.reserve(points.size()); - for(glm::vec2 point : points) - { - b_points.push_back(v2b(point)); - } - if (loop) - { - shape.CreateLoop(b_points.data(), static_cast(b_points.size())); - } - else - { - shape.CreateChain(b_points.data(), static_cast(b_points.size())); - } - - createBody(&shape); -} - -void Collisionable::setCollisionFriction(float amount) -{ - if (!body) - return; - for(b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext()) - { - f->SetFriction(amount); - } -} - -void Collisionable::setCollisionFilter(uint16_t category_bits, uint16_t mask_bits) -{ - if (!body) - return; - b2Filter filter; - filter.categoryBits = category_bits; - filter.maskBits = mask_bits; - for(b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext()) - { - f->SetFilterData(filter); - } -} - -void Collisionable::setCollisionPhysics(bool wants_enable_physics, bool wants_static_physics) -{ - enable_physics = wants_enable_physics; - static_physics = wants_static_physics; - - if (!body) - return; - - for(b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext()) - f->SetSensor(!enable_physics); - body->SetType(static_physics ? b2_kinematicBody : b2_dynamicBody); -} - -void Collisionable::createBody(b2Shape* shape) -{ - if (body) - { - while(body->GetFixtureList()) - body->DestroyFixture(body->GetFixtureList()); - }else{ - b2BodyDef bodyDef; - bodyDef.type = static_physics ? b2_kinematicBody : b2_dynamicBody; - bodyDef.userData = this; - bodyDef.allowSleep = false; - bodyDef.position = v2b(this->position); - bodyDef.angle = glm::radians(this->rotation); - body = CollisionManager::world->CreateBody(&bodyDef); - } - - b2FixtureDef shapeDef; - shapeDef.shape = shape; - shapeDef.density = 1.0f; - shapeDef.friction = 0.0f; - shapeDef.isSensor = !enable_physics; - body->CreateFixture(&shapeDef); -} - -void Collisionable::destroyBody() -{ - if (body) { - this->position = getPosition(); - this->rotation = getRotation(); - CollisionManager::world->DestroyBody(body); - } - body = nullptr; -} - -void Collisionable::collide(Collisionable* /*target*/, float /*force*/) -{ -} - -void Collisionable::setPosition(glm::vec2 position) -{ - if (body == NULL) - this->position = position; - else - body->SetTransform(v2b(position), body->GetAngle()); -} - -glm::vec2 Collisionable::getPosition() const -{ - if (body == NULL) return position; - return b2v(body->GetPosition()); -} - -void Collisionable::setRotation(float angle) -{ - if (body == NULL) - rotation = angle; - else - body->SetTransform(body->GetPosition(), glm::radians(angle)); -} - -float Collisionable::getRotation() const -{ - if (body == NULL) return rotation; - return glm::degrees(body->GetAngle()); -} - -void Collisionable::setVelocity(glm::vec2 velocity) -{ - if (body == NULL) return; - body->SetLinearVelocity(v2b(velocity)); -} -glm::vec2 Collisionable::getVelocity() const -{ - if (body == NULL) return glm::vec2(0, 0); - return b2v(body->GetLinearVelocity()); -} - -void Collisionable::setAngularVelocity(float velocity) -{ - if (body == NULL) return; - body->SetAngularVelocity(glm::radians(velocity)); -} -float Collisionable::getAngularVelocity() const -{ - if (body == NULL) return 0; - return glm::degrees(body->GetAngularVelocity()); -} - -void Collisionable::applyImpulse(glm::vec2 position, glm::vec2 impulse) -{ - if (body == NULL) return; - body->ApplyLinearImpulse(v2b(impulse), v2b(position), true); -} - -bool Collisionable::hasCollisionShape() -{ - return body != nullptr; -} - -glm::vec2 Collisionable::toLocalSpace(glm::vec2 v) const -{ - if (body == NULL) return glm::vec2(0, 0); - return b2v(body->GetLocalPoint(v2b(v))); -} -glm::vec2 Collisionable::toWorldSpace(glm::vec2 v) const -{ - if (body == NULL) return glm::vec2(0, 0); - return b2v(body->GetWorldPoint(v2b(v))); -} - -std::vector Collisionable::getCollisionShape() const -{ - std::vector ret; - if (body == NULL) return ret; - b2Fixture* f = body->GetFixtureList(); - b2Shape* s = f->GetShape(); - switch(s->GetType()) - { - case b2Shape::e_circle: - { - b2CircleShape* cs = static_cast(s); - float radius = cs->m_radius * BOX2D_SCALE; - for(int n=0; n<32; n++) - ret.push_back(glm::vec2(sin(float(n)/32.f*float(M_PI)*2) * radius, cos(float(n)/32.f*float(M_PI)*2) * radius)); - } - break; - case b2Shape::e_polygon: - { - b2PolygonShape* cs = static_cast(s); - for(int n=0; nGetVertexCount(); n++) - ret.push_back(b2v(cs->GetVertex(n))); - } - break; - default: - break; - } - return ret; -} diff --git a/src/collisionable.h b/src/collisionable.h deleted file mode 100644 index fb0a17e7..00000000 --- a/src/collisionable.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef COLLISIONABLE_H -#define COLLISIONABLE_H - -#include "P.h" - -class b2World; -class b2Body; -class b2Shape; - -class Collisionable; -class CollisionManager -{ -public: - static void initialize(); - static void handleCollisions(float delta); - static PVector queryArea(glm::vec2 lowerBound, glm::vec2 upperBound); -private: - static b2World* world; - - friend class Collisionable; -}; - -class Collisionable : public virtual PObject -{ -private: - glm::vec2 position{}; - float rotation = 0.0f; - b2Body* body; - bool enable_physics; - bool static_physics; - - void createBody(b2Shape* shape); - void destroyBody(); -public: - Collisionable(float radius); - Collisionable(glm::vec2 box_size, glm::vec2 box_origin = glm::vec2(0, 0)); - Collisionable(const std::vector& shape); - virtual ~Collisionable(); - virtual void collide(Collisionable* target, float force); - - void setCollisionRadius(float radius); - void setCollisionBox(glm::vec2 box_size, glm::vec2 box_origin = glm::vec2(0, 0)); - void setCollisionShape(const std::vector& shape); - void setCollisionChain(const std::vector& points, bool loop); - void setCollisionPhysics(bool enable_physics, bool static_physics); - void setCollisionFriction(float amount); - void setCollisionFilter(uint16_t category_bits, uint16_t mask_bits); //Collision happens if (A->category_bits & B->mask_bits) && (B->category_bits & A->mask_bits) - - void setPosition(glm::vec2 v); - glm::vec2 getPosition() const; - void setRotation(float angle); - float getRotation() const; - void setVelocity(glm::vec2 velocity); - glm::vec2 getVelocity() const; - void setAngularVelocity(float velocity); - float getAngularVelocity() const; - void applyImpulse(glm::vec2 position, glm::vec2 impulse); - bool hasCollisionShape(); - - glm::vec2 toLocalSpace(glm::vec2 v) const; - glm::vec2 toWorldSpace(glm::vec2 v) const; - - std::vector getCollisionShape() const; //For debugging - - float multiplayer_replication_object_significant_range; - - friend class CollisionManager; -}; - -#endif // COLLISIONABLE_H diff --git a/src/components/collision.h b/src/components/collision.h new file mode 100644 index 00000000..5e4ee125 --- /dev/null +++ b/src/components/collision.h @@ -0,0 +1,85 @@ +#pragma once + +#include + + +class b2Body; +namespace sp { +namespace multiplayer { class TransformReplication; class PhysicsReplication; } +class CollisionSystem; + +// Transform component, to give an entity a position and rotation in the 3D world. +class Transform +{ +public: + glm::vec2 getPosition() const { return position; } + float getRotation() const { return rotation; } + + void setPosition(glm::vec2 v) { position = v; position_user_set = true; multiplayer_dirty = true; } + void setRotation(float angle) { rotation = angle; rotation_user_set = true; multiplayer_dirty = true; } + // Only use the NoReplication version if the client simulates the same movement. + void setPositionNoReplication(glm::vec2 v) { position = v; position_user_set = true; } + void setRotationNoReplication(float angle) { rotation = angle; rotation_user_set = true; } +private: + bool position_user_set = false; + bool rotation_user_set = false; + bool multiplayer_dirty = false; + + glm::vec2 position{}; + float rotation = 0.0f; + + float last_send_time = 0.0f; + glm::vec2 last_send_position{}; + float last_send_rotation = 0.0f; + + friend class sp::CollisionSystem; + friend class sp::multiplayer::TransformReplication; +}; + +// The physics component will give the entity a physical presents in the physic simulation +// this includes collision feedback. A physics component does nothing on it's own, it needs a Position component as well. +// The position component will be updated by the physics system on each physics step. Updating the position component from another location will force the +// physics to move the object to that position. +class Physics +{ +public: + enum class Type { + Sensor, + Dynamic, + Static, + }; + enum class Shape { + Circle, + Rectangle, + }; + Type getType() const { return type; } + void setType(Type type) { if (type == this->type) return; this->type = type; physics_dirty = true; multiplayer_dirty = true; } + void setCircle(Type type, float radius) { if (type == this->type && shape == Shape::Circle && size.x == radius) return; this->type = type; shape = Shape::Circle; size.x = radius; size.y = radius; physics_dirty = true; multiplayer_dirty = true; } + void setRectangle(Type type, glm::vec2 new_size) { if (type == this->type && shape == Shape::Rectangle && size == new_size) return; this->type = type; shape = Shape::Circle; size = new_size; physics_dirty = true; multiplayer_dirty = true; } + Shape getShape() const { return shape; } + glm::vec2 getSize() const { return size; } + + glm::vec2 getVelocity() const { return linear_velocity; } + float getAngularVelocity() const { return angular_velocity; } + + void setVelocity(glm::vec2 velocity) { linear_velocity_user_set = true; linear_velocity = velocity; } + void setAngularVelocity(float velocity) { angular_velocity_user_set = true; angular_velocity = velocity; } +private: + bool physics_dirty = true; + bool multiplayer_dirty = false; + + Type type = Type::Sensor; + Shape shape = Shape::Circle; + glm::vec2 size{1.0, 1.0}; + + b2Body* body = nullptr; + glm::vec2 linear_velocity{}; + float angular_velocity = 0.0f; + bool linear_velocity_user_set = false; + bool angular_velocity_user_set = false; + + friend class sp::CollisionSystem; + friend class sp::multiplayer::PhysicsReplication; +}; + +} \ No newline at end of file diff --git a/src/components/multiplayer.h b/src/components/multiplayer.h new file mode 100644 index 00000000..dcf52b3a --- /dev/null +++ b/src/components/multiplayer.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + + +// Component to indicate that this entity exists on the server but we are running on the client. +// And indicate which index the server has for this entity. +class ServerIndex +{ +public: + uint32_t index = std::numeric_limits::max(); + uint32_t version = std::numeric_limits::max(); +}; diff --git a/src/container/bitset.h b/src/container/bitset.h new file mode 100644 index 00000000..a4187ec9 --- /dev/null +++ b/src/container/bitset.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + + +namespace sp { + +class Bitset final +{ +public: + bool has(uint32_t index) + { + auto idx = storageIndex(index); + if (idx >= storage.size()) return false; + return storage[idx] & storageMask(index); + } + + void set(uint32_t index) + { + auto idx = storageIndex(index); + if (idx >= storage.size()) storage.resize(idx + 1, 0); + storage[idx] |= storageMask(index); + } + + void reset(uint32_t index) + { + auto idx = storageIndex(index); + if (idx >= storage.size()) return; + storage[idx] &=~storageMask(index); + } + + void clear(uint32_t index) + { + storage.clear(); + } +private: + using StorageType = uint32_t; + size_t storageIndex(uint32_t index) { return index / (8 * sizeof(StorageType));} + StorageType storageMask(uint32_t index) { return 1 << (index % (8 * sizeof(StorageType)));} + + std::vector storage; +}; + +} \ No newline at end of file diff --git a/src/container/chunkedvector.h b/src/container/chunkedvector.h new file mode 100644 index 00000000..c1dff174 --- /dev/null +++ b/src/container/chunkedvector.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include + + +namespace sp { + +template class ChunkedVector final +{ +public: + size_t size() { + return count; + } + + template void emplace_back(ARGS&&... args) { + if (count == chunks.size() * CHUNK_SIZE) + chunks.push_back(new Chunk()); + auto ptr = &(*this)[count]; + new (ptr)T(std::forward(args)...); + count++; + } + void pop_back() { + count -= 1; + (&(*this)[count])->~T(); + } + + T& operator[](size_t index) { + auto& chunk = chunks[index / CHUNK_SIZE]; + auto& storage = chunk->elements[index % CHUNK_SIZE]; + return *((T*)&storage); + } + const T& operator[](size_t index) const { + auto& chunk = chunks[index / CHUNK_SIZE]; + auto& storage = chunk->elements[index % CHUNK_SIZE]; + return *((const T*)&storage); + } + + T& back() { + return (*this)[count - 1]; + } + const T& back() const { + return (*this)[count - 1]; + } +private: + using Storage = uint8_t[sizeof(T)]; + struct Chunk { + Storage elements[CHUNK_SIZE]; + }; + + size_t count = 0; + std::vector chunks; +}; + +} \ No newline at end of file diff --git a/src/container/sparseset.h b/src/container/sparseset.h new file mode 100644 index 00000000..4d9d6afc --- /dev/null +++ b/src/container/sparseset.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include "chunkedvector.h" + + +namespace sp { + +// A sparseset is a more optimized version of a map<> with an important constrain: +// The key has to be an integer type with a limited range, the smaller the better. +// This gives optimized cache performance when iterating over all entities, but still allows quick lookup of individual entries. +template class SparseSet final +{ +public: + bool has(uint32_t index) + { + if (index >= sparse.size()) + return false; + return sparse[index] < dense.size(); + } + + T& get(uint32_t index) + { + return data[sparse[index]]; + } + + bool set(uint32_t index, const T& value) + { + if (has(index)) { + data[sparse[index]] = value; + return false; + } + if (sparse.size() <= index) + sparse.resize(index + 1, std::numeric_limits::max()); + if (free_dense != no_free_dense) { + // Reuse a free slot + auto new_free = dense[free_dense] & ~free_mark; + sparse[index] = free_dense; + dense[free_dense] = index; + data[free_dense] = value; + free_dense = new_free; + } else { + // Append to the data + sparse[index] = dense.size(); + dense.push_back(index); + data.emplace_back(value); + } + return true; + } + + bool set(uint32_t index, T&& value) + { + if (has(index)) { + data[sparse[index]] = std::move(value); + return false; + } + if (sparse.size() <= index) + sparse.resize(index + 1, std::numeric_limits::max()); + if (free_dense != no_free_dense) { + // Reuse a free slot + auto new_free = dense[free_dense] & ~free_mark; + sparse[index] = free_dense; + dense[free_dense] = index; + data[free_dense] = std::move(value); + free_dense = new_free; + } else { + // Append to the data + sparse[index] = dense.size(); + dense.push_back(index); + data.emplace_back(std::move(value)); + } + return true; + } + + bool remove(uint32_t index) + { + if (!has(index)) + return false; + auto new_free = sparse[index]; + sparse[index] = free_mark; + dense[new_free] = free_dense | free_mark; + free_dense = new_free; + return true; + } + + class Iterator + { + public: + Iterator(SparseSet& _set, size_t _dense_index) : set(_set), dense_index(_dense_index) { + if (_dense_index == 0) skipFree(); + } + + bool operator!=(const Iterator& other) const { return dense_index != other.dense_index; } + void operator++() { dense_index++; skipFree(); } + std::pair operator*() { return {set.dense[dense_index], set.data[dense_index]}; } + std::pair operator*() const { return {set.dense[dense_index], set.data[dense_index]}; } + + bool atEnd() { return dense_index >= set.dense.size(); } + private: + void skipFree() { + while(dense_index < set.dense.size() && set.dense[dense_index] & free_mark) + dense_index++; + } + + SparseSet& set; + size_t dense_index; + }; + + Iterator begin() { return Iterator(*this, 0); } + Iterator end() { return Iterator(*this, dense.size()); } + + size_t size() { return data.size(); } +private: + std::vector sparse; + std::vector dense; + ChunkedVector data; + static constexpr uint32_t free_mark = 0x80000000; + static constexpr uint32_t no_free_dense = std::numeric_limits::max() & ~free_mark; + uint32_t free_dense = no_free_dense; +}; + +} \ No newline at end of file diff --git a/src/ecs/component.cpp b/src/ecs/component.cpp new file mode 100644 index 00000000..5c0e6b21 --- /dev/null +++ b/src/ecs/component.cpp @@ -0,0 +1,25 @@ +#include "ecs/component.h" + +namespace sp::ecs { + +static ComponentStorageBase* all_component_storage = nullptr; + +ComponentStorageBase::ComponentStorageBase() +{ + this->next = all_component_storage; + all_component_storage = this; +} + +void ComponentStorageBase::destroyAll(uint32_t index) +{ + for(auto storage = all_component_storage; storage; storage = storage->next) + storage->destroy(index); +} + +void ComponentStorageBase::dumpDebugInfo() +{ + for(auto storage = all_component_storage; storage; storage = storage->next) + storage->dumpDebugInfoImpl(); +} + +} \ No newline at end of file diff --git a/src/ecs/component.h b/src/ecs/component.h new file mode 100644 index 00000000..551a80e9 --- /dev/null +++ b/src/ecs/component.h @@ -0,0 +1,46 @@ +#pragma once + +#include "container/sparseset.h" +#include "logging.h" + + +namespace sp::ecs { +template class ComponentReplication; + +class ComponentStorageBase { +public: + ComponentStorageBase(); + + static void dumpDebugInfo(); +private: + ComponentStorageBase* next = nullptr; +protected: + static void destroyAll(uint32_t index); + virtual void destroy(uint32_t index) = 0; + virtual void dumpDebugInfoImpl() = 0; + + friend class Entity; +}; + +template class ComponentStorage : public ComponentStorageBase { + void destroy(uint32_t index) override + { + sparseset.remove(index); + } + + virtual void dumpDebugInfoImpl() override + { + if (storage.sparseset.size()) + LOG(Debug, "Component:", typeid(T).name(), " Count: ", storage.sparseset.size()); + } + + SparseSet sparseset; + + static inline ComponentStorage storage; + + friend class Entity; + template friend class Query; + friend class ComponentReplication; +}; + +} \ No newline at end of file diff --git a/src/ecs/entity.cpp b/src/ecs/entity.cpp new file mode 100644 index 00000000..4bd2a6b0 --- /dev/null +++ b/src/ecs/entity.cpp @@ -0,0 +1,101 @@ +#include "entity.h" +#include "component.h" +#include "logging.h" +#include + +namespace sp::ecs { + +std::vector Entity::entity_version; +std::vector Entity::free_list; + +Entity Entity::create() +{ + Entity e; + if (free_list.empty()) { + e.index = entity_version.size(); + e.version = 0; + entity_version.push_back(0); + } else { + e.index = free_list.back(); + free_list.pop_back(); + entity_version[e.index] &=~destroyed_flag; + e.version = entity_version[e.index]; + } + return e; +} + +Entity Entity::fromIndex(uint32_t index) +{ + Entity e; + e.index = index; + e.version = entity_version[index]; + return e; +} + +Entity::operator bool() const +{ + if (index == no_index) + return false; + return entity_version[index] == version; +} + +bool Entity::operator==(const Entity& other) const { + auto bt = bool(*this); + auto bo = bool(other); + if (bt != bo) + return false; + if (!bt) + return true; + return index == other.index; +} + +bool Entity::operator!=(const Entity& other) const { + auto bt = bool(*this); + auto bo = bool(other); + if (bt != bo) + return true; + if (!bt) + return false; + return index != other.index; +} + +void Entity::destroy() +{ + if (!*this) + return; + ComponentStorageBase::destroyAll(index); + + // By increasing the version number, everything else will know this entity no longer exists. + entity_version[index] = (entity_version[index] + 1) | destroyed_flag; + free_list.push_back(index); +} + +string Entity::toString() const +{ + if (!*this) + return ""; + return string(index) + ":" + string(version); +} + +Entity Entity::fromString(const string& s) +{ + auto idx = s.find(':'); + if (idx < 0) + return {}; + return forced(s.substr(0, idx).toInt(), s.substr(idx + 1).toInt()); +} + +void Entity::destroyAllEntities() +{ + for(size_t index = 0; index < entity_version.size(); index++) { + if (!(entity_version[index] & destroyed_flag)) + forced(index, entity_version[index]).destroy(); + } +} + +void Entity::dumpDebugInfo() +{ + LOG(Debug, "Entity count:", entity_version.size() - free_list.size(), " Free entities:", free_list.size()); +} + +} diff --git a/src/ecs/entity.h b/src/ecs/entity.h new file mode 100644 index 00000000..3ca86f90 --- /dev/null +++ b/src/ecs/entity.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +#include "component.h" + + +class MultiplayerObject; +class GameServer; +class GameClient; +namespace sp::ecs { + +// An entity is a lightweight, copyable reference to entity components. +// Entities have to be created with the create() function, and explicitly destroy()ed. +class Entity final { +public: + Entity() = default; + + static Entity create(); + void destroy(); + + explicit operator bool() const; + + template T* getComponent() + { + if (!bool(*this) || !hasComponent()) + return nullptr; + return &ComponentStorage::storage.sparseset.get(index); + } + template const T* getComponent() const + { + if (!bool(*this) || !hasComponent()) + return nullptr; + return &ComponentStorage::storage.sparseset.get(index); + } + template T& addComponent() + { + ComponentStorage::storage.sparseset.set(index, {}); + return ComponentStorage::storage.sparseset.get(index); + } + template T& addComponent(ARGS&&... args) + { + ComponentStorage::storage.sparseset.set(index, T{std::forward(args)...}); + return ComponentStorage::storage.sparseset.get(index); + } + template T& getOrAddComponent() + { + if (!hasComponent()) + ComponentStorage::storage.sparseset.set(index, {}); + return ComponentStorage::storage.sparseset.get(index); + } + template bool hasComponent() const + { + return ComponentStorage::storage.sparseset.has(index); + } + template void removeComponent() + { + ComponentStorage::storage.sparseset.remove(index); + } + + bool operator==(const Entity& other) const; + bool operator!=(const Entity& other) const; + + uint32_t getIndex() const { return index; } // You should never need this, but the multiplayer code does need it. + uint32_t getVersion() const { return version; } // You should never need this, but the multiplayer code does need it. + static Entity forced(uint32_t index, uint32_t version) { auto e = Entity(); e.index = index; e.version = version; return e; } + + string toString() const; + static Entity fromString(const string& s); + + static void destroyAllEntities(); + + static void dumpDebugInfo(); + + static constexpr uint32_t no_index = std::numeric_limits::max(); +private: + static constexpr uint32_t destroyed_flag = 1 << (std::numeric_limits::digits - 1); + + uint32_t index = no_index; + uint32_t version = no_index; + + static Entity fromIndex(uint32_t index); + static std::vector entity_version; + static std::vector free_list; + + template friend class Query; + + friend class ::MultiplayerObject; // We need to be a friend for network replication. + friend class ::GameServer; // We need to be a friend for network replication. + friend class ::GameClient; // We need to be a friend for network replication. +}; + +} + +namespace std { +template <> struct hash { + size_t operator()(const sp::ecs::Entity& e) const { + return hash{}(e.getIndex()) + hash{}(e.getVersion()); + } +}; +} \ No newline at end of file diff --git a/src/ecs/multiplayer.h b/src/ecs/multiplayer.h new file mode 100644 index 00000000..71cebd05 --- /dev/null +++ b/src/ecs/multiplayer.h @@ -0,0 +1,88 @@ +#pragma once + +#include "io/dataBuffer.h" +#include "ecs/entity.h" +#include "multiplayer_internal.h" + +namespace sp::ecs { + +class ComponentReplicationBase { +public: + uint16_t component_index = 0; + + //Called on the server so reused component slots get properly send if an entity is destroyed and reused within the same tick. + // And that destroyed entities don't need to send "destroyed component" packets for each component. + virtual void onEntityDestroyed(uint32_t index) = 0; + + virtual void sendAll(sp::io::DataBuffer& packet) = 0; + virtual void update(sp::io::DataBuffer& packet) = 0; + virtual void receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) = 0; //Called from client + virtual void remove(sp::ecs::Entity entity) = 0; //Called from client +}; + +//Simple replication setup that just sends over the whole component each time it is changed. +template class ComponentReplication : public ComponentReplicationBase +{ +public: + sp::SparseSet component_copy; + + void onEntityDestroyed(uint32_t index) override + { + component_copy.remove(index); + } + + void sendAll(sp::io::DataBuffer& packet) override + { + for(auto [index, data] : sp::ecs::ComponentStorage::storage.sparseset) + { + packet << CMD_ECS_SET_COMPONENT << component_index << index << data; + } + } + + void update(sp::io::DataBuffer& packet) override + { + for(auto [index, data] : sp::ecs::ComponentStorage::storage.sparseset) + { + if (!component_copy.has(index) || component_copy.get(index) != data) { + component_copy.set(index, data); + packet << CMD_ECS_SET_COMPONENT << component_index << index << data; + } + } + for(auto [index, data] : component_copy) + { + if (!sp::ecs::ComponentStorage::storage.sparseset.has(index)) { + component_copy.remove(index); + packet << CMD_ECS_DEL_COMPONENT << component_index << index; + } + } + } + + void receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) override + { + T data; + packet >> data; + entity.addComponent(data); + } + + void remove(sp::ecs::Entity entity) override + { + entity.removeComponent(); + } +}; + +class MultiplayerReplication { +public: + template static void registerComponentReplication() { + auto t = new T(); + t->component_index = list.size(); + list.push_back(t); + } + +private: + static inline std::vector list; + + friend class ::GameClient; + friend class ::GameServer; +}; + +} diff --git a/src/ecs/query.h b/src/ecs/query.h new file mode 100644 index 00000000..12b42769 --- /dev/null +++ b/src/ecs/query.h @@ -0,0 +1,82 @@ +#pragma once + +#include "ecs/entity.h" +#include "ecs/component.h" + + +namespace sp::ecs { + +template struct optional {}; + +template struct optional_info { + using base_type = T; + using ref_type = T&; + static constexpr bool value = false; +}; +template struct optional_info> { + using base_type = T; + using ref_type = T*; + static constexpr bool value = true; +}; + +template class Query { +public: + class Iterator { + public: + Iterator(int) : iterator(ComponentStorage::storage.sparseset.begin()) { while(checkForSkip()) {} } + Iterator() : iterator(ComponentStorage::storage.sparseset.end()) {} + + bool operator!=(const Iterator& other) const { return iterator != other.iterator; } + void operator++() { ++iterator; while(checkForSkip()) {} } + std::tuple::ref_type...> operator*() { + auto [index, primary] = *iterator; + return {Entity::fromIndex(index), primary, getComponent(index)...}; + } + + private: + template typename optional_info::ref_type getComponent(uint32_t index) + { + if constexpr (optional_info::value) + { + if (!ComponentStorage::base_type>::storage.sparseset.has(index)) + return nullptr; + return &ComponentStorage::base_type>::storage.sparseset.get(index); + } else { + return ComponentStorage::storage.sparseset.get(index); + } + } + + bool checkForSkip() { + if (iterator.atEnd()) return false; + if constexpr (sizeof...(T) > 0) { + if (!checkIfHasAll()) { + ++iterator; + return true; + } + } + return false; + } + template bool checkIfHasAll() { + if constexpr (!optional_info::value) { + auto index = (*iterator).first; + if (!ComponentStorage::storage.sparseset.has(index)) + return false; + } + if constexpr (sizeof...(ARGS) > 0) + return checkIfHasAll(); + return true; + } + + typename SparseSet::Iterator iterator; + }; + + Iterator begin() { + return Iterator(0); + } + + Iterator end() { + return Iterator(); + } +}; + +} \ No newline at end of file diff --git a/src/ecs/system.h b/src/ecs/system.h new file mode 100644 index 00000000..0bdac265 --- /dev/null +++ b/src/ecs/system.h @@ -0,0 +1,10 @@ +#pragma once + +namespace sp::ecs { + +class System { +public: + virtual void update(float delta) = 0; +}; + +} \ No newline at end of file diff --git a/src/engine.cpp b/src/engine.cpp index 6c13fc5c..dfa802fd 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,13 +1,13 @@ #include "engine.h" #include "random.h" #include "Updatable.h" -#include "collisionable.h" #include "audio/source.h" #include "io/keybinding.h" #include "soundManager.h" #include "windowManager.h" -#include "scriptInterface.h" #include "multiplayer_server.h" +#include "ecs/entity.h" +#include "systems/collision.h" #include #include @@ -17,6 +17,12 @@ #include "steam/steam_api_flat.h" #endif +#ifdef __APPLE__ +#include +#include +#include +#endif + #ifdef DEBUG #include int DEBUG_PobjCount; @@ -29,6 +35,48 @@ Engine::Engine() { engine = this; +#ifdef __APPLE__ + // TODO: Find a proper solution. + // Seems to be non-NULL even outside of a proper bundle. + CFBundleRef bundle = CFBundleGetMainBundle(); + if (bundle) + { + char bundle_path[PATH_MAX], exe_path[PATH_MAX]; + + CFURLRef bundleURL = CFBundleCopyBundleURL(bundle); + CFURLGetFileSystemRepresentation(bundleURL, true, (unsigned char*)bundle_path, PATH_MAX); + CFRelease(bundleURL); + + uint32_t size = sizeof(exe_path); + if (_NSGetExecutablePath(exe_path, &size) != 0) + { + fprintf(stderr, "Failed to get executable path.\n"); + return 1; + } + + char *exe_realpath = realpath(exe_path, NULL); + char *exe_dir = dirname(exe_realpath); + + if (strcmp(exe_dir, bundle_path)) + { + char resources_path[PATH_MAX]; + + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); + CFURLGetFileSystemRepresentation(resourcesURL, true, (unsigned char*)resources_path, PATH_MAX); + CFRelease(resourcesURL); + + chdir(resources_path); + } + else + { + chdir(exe_dir); + } + + free(exe_realpath); + free(exe_dir); + } +#endif + #ifdef STEAMSDK if (SteamAPI_RestartAppIfNecessary(1907040)) exit(1); @@ -77,7 +125,6 @@ Engine::Engine() atexit(SDL_Quit); initRandom(); - CollisionManager::initialize(); gameSpeed = 1.0f; running = true; elapsedTime = 0.0f; @@ -130,23 +177,25 @@ void Engine::runMainLoop() LOG(DEBUG) << "Object count: " << DEBUG_PobjCount << " " << updatableList.size(); #endif - float delta = frame_timer.restart(); - if (delta > 0.5f) - delta = 0.5f; - if (delta < 0.001f) - delta = 0.001f; - delta *= gameSpeed; + auto realtime_delta = frame_timer.restart(); + auto update_delta = realtime_delta; + if (update_delta > 0.5f) + update_delta = 0.5f; + if (update_delta < 0.001f) + update_delta = 0.001f; + update_delta *= gameSpeed; foreach(Updatable, u, updatableList) - u->update(delta); - elapsedTime += delta; - CollisionManager::handleCollisions(delta); - ScriptObject::clearDestroyedObjects(); + u->update(update_delta); + for(auto system : systems) + system->update(update_delta); + sp::CollisionSystem::update(update_delta); + elapsedTime += update_delta; soundManager->updateTick(); #ifdef STEAMSDK SteamAPI_RunCallbacks(); #endif - std::this_thread::sleep_for(std::chrono::duration(1.f/60.f - delta)); + std::this_thread::sleep_for(std::chrono::duration(1.f/60.f - realtime_delta)); } }else{ sp::audio::Source::startAudioSystem(); @@ -181,11 +230,12 @@ void Engine::runMainLoop() foreach(Updatable, u, updatableList) { u->update(delta); } + for(auto system : systems) + system->update(delta); elapsedTime += delta; engine_timing.update = engine_timing_stopwatch.restart(); - CollisionManager::handleCollisions(delta); + sp::CollisionSystem::update(delta); engine_timing.collision = engine_timing_stopwatch.restart(); - ScriptObject::clearDestroyedObjects(); soundManager->updateTick(); #ifdef STEAMSDK SteamAPI_RunCallbacks(); @@ -237,6 +287,9 @@ void Engine::handleEvent(SDL_Event& event) } printf("%4d %s\n",grand_total,"All PObjects"); printf("------------------------\n"); + + sp::ecs::Entity::dumpDebugInfo(); + sp::ecs::ComponentStorageBase::dumpDebugInfo(); } #endif diff --git a/src/engine.h b/src/engine.h index 027665ec..8ef6535f 100644 --- a/src/engine.h +++ b/src/engine.h @@ -3,6 +3,7 @@ #include #include "stringImproved.h" +#include "ecs/system.h" #include "P.h" #ifdef WIN32 @@ -36,6 +37,7 @@ class Engine #ifdef WIN32 std::unique_ptr exchndl; #endif + std::vector systems; public: Engine(); ~Engine(); @@ -48,8 +50,13 @@ class Engine void registerObject(string name, P obj); P getObject(string name); + template void registerSystem() { + systems.push_back(new T()); + } + void runMainLoop(); void shutdown(); + bool isRunning() { return running; } private: void handleEvent(SDL_Event& event); }; diff --git a/src/io/dataBuffer.h b/src/io/dataBuffer.h index 9e4c6477..74586cac 100644 --- a/src/io/dataBuffer.h +++ b/src/io/dataBuffer.h @@ -101,6 +101,11 @@ class DataBuffer writeVLQu(i); } + void write(uint64_t i) + { + writeVLQu64(i); + } + void write(const float f) { appendRaw(&f, sizeof(f)); @@ -124,11 +129,11 @@ class DataBuffer { appendRaw(other.buffer.data(), other.buffer.size()); } - - template void read(T& value, ARGS&... args) - { - read(value); - read(args...); + + template std::tuple read() { + std::tuple result; + std::apply([this](auto&... v) { ((*this >> v), ...); }, result); + return result; } void read(bool& b) @@ -168,6 +173,11 @@ class DataBuffer i = readVLQu(); } + void read(uint64_t& i) + { + i = readVLQu64(); + } + void read(float& f) { if (read_index + sizeof(f) > buffer.size()) { f = 0; return; } @@ -207,6 +217,7 @@ class DataBuffer DataBuffer& operator <<(uint16_t data) { write(data); return *this; } DataBuffer& operator <<(int32_t data) { write(data); return *this; } DataBuffer& operator <<(uint32_t data) { write(data); return *this; } + DataBuffer& operator <<(uint64_t data) { write(data); return *this; } DataBuffer& operator <<(float data) { write(data); return *this; } DataBuffer& operator <<(double data) { write(data); return *this; } DataBuffer& operator <<(std::string_view data) { write(data); return *this; } @@ -218,9 +229,13 @@ class DataBuffer DataBuffer& operator >>(uint16_t& data) { read(data); return *this; } DataBuffer& operator >>(int32_t& data) { read(data); return *this; } DataBuffer& operator >>(uint32_t& data) { read(data); return *this; } + DataBuffer& operator >>(uint64_t& data) { read(data); return *this; } DataBuffer& operator >>(float& data) { read(data); return *this; } DataBuffer& operator >>(double& data) { read(data); return *this; } DataBuffer& operator >>(string& data) { read(data); return *this; } + + template>> DataBuffer& operator <<(T data) { write(int(data)); return *this; } + template>> DataBuffer& operator >>(T& data) { int n = 0; read(n); data = T(n); return *this; } private: void writeVLQu(uint32_t v) { if (v >= (1 << 28)) @@ -234,6 +249,28 @@ class DataBuffer buffer.push_back((v & 0x7F)); } + void writeVLQu64(uint64_t v) { + if (v >= (1ULL << 63)) + buffer.push_back((v >> 63) | 0x80); + if (v >= (1ULL << 56)) + buffer.push_back((v >> 56) | 0x80); + if (v >= (1ULL << 49)) + buffer.push_back((v >> 49) | 0x80); + if (v >= (1ULL << 42)) + buffer.push_back((v >> 42) | 0x80); + if (v >= (1ULL << 35)) + buffer.push_back((v >> 35) | 0x80); + if (v >= (1ULL << 28)) + buffer.push_back((v >> 28) | 0x80); + if (v >= (1ULL << 21)) + buffer.push_back((v >> 21) | 0x80); + if (v >= (1ULL << 14)) + buffer.push_back((v >> 14) | 0x80); + if (v >= (1ULL << 7)) + buffer.push_back((v >> 7) | 0x80); + buffer.push_back((v & 0x7F)); + } + void writeVLQs(int32_t v) { if (v < 0) writeVLQu((uint32_t(-v) << 1) | 1); @@ -255,6 +292,20 @@ class DataBuffer return result; } + uint64_t readVLQu64() + { + uint64_t result{0}; + uint8_t u; + do + { + result <<= 7; + if (read_index >= buffer.size()) { return result; } + u = buffer[read_index++]; + result |= u & 0x7F; + } while(u & 0x80); + return result; + } + int32_t readVLQs() { uint32_t v = readVLQu(); diff --git a/src/logging.cpp b/src/logging.cpp index a1b5de27..30059f7e 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -16,11 +16,9 @@ #define SP_LOGGING_FALLBACK_STDIO 0 #endif -#if SP_LOGGING_FALLBACK_STDIO #include -#else #include -#endif + namespace { @@ -34,28 +32,31 @@ namespace "[CRITICAL]: " }; - void sdlCallback(void* userdata, int /*category*/, SDL_LogPriority priority, const char* message) + void stdioCallback(void* userdata, int /*category*/, SDL_LogPriority priority, const char* message) { -#if SP_LOGGING_FALLBACK_STDIO auto stream = static_cast(userdata); auto write = [stream](const void* buffer, size_t size, size_t num) { return fwrite(buffer, size, num, stream); }; -#else + const auto& label = priority_labels[priority]; + write(label.data(), label.size(), 1); + write(message, SDL_strlen(message), 1); + write("\n", 1, 1); + fflush(stream); + } + + void sdlCallback(void* userdata, int /*category*/, SDL_LogPriority priority, const char* message) + { auto stream = static_cast(userdata); auto write = [stream](const void* buffer, size_t size, size_t num) { return SDL_RWwrite(stream, buffer, size, num); }; -#endif const auto& label = priority_labels[priority]; write(label.data(), label.size(), 1); write(message, SDL_strlen(message), 1); write("\n", 1, 1); -#if SP_LOGGING_FALLBACK_STDIO - fflush(stream); -#endif } constexpr SDL_LogPriority asSDLPriority(ELogLevel level) @@ -128,25 +129,35 @@ void Logging::setLogLevel(ELogLevel level) void Logging::setLogFile(std::string_view filename) { - SDL_LogOutputFunction current = nullptr; - void* current_data = nullptr; - SDL_LogGetOutputFunction(¤t, ¤t_data); - if (current == &sdlCallback) - { -#if SP_LOGGING_FALLBACK_STDIO - auto stream = static_cast(current_data); - fclose(stream); -#else - auto stream = static_cast(current_data); - SDL_RWclose(stream); -#endif - } + closeCurrentLogStream(); #if SP_LOGGING_FALLBACK_STDIO auto handle = fopen(filename.data(), "wt"); + SDL_LogSetOutputFunction(&stdioCallback, handle); #else auto handle = SDL_RWFromFile(filename.data(), "wt"); + SDL_LogSetOutputFunction(&sdlCallback, handle); #endif +} - SDL_LogSetOutputFunction(&sdlCallback, handle); +void Logging::setLogStdout() +{ + closeCurrentLogStream(); + SDL_LogSetOutputFunction(&stdioCallback, stdout); } + +void Logging::closeCurrentLogStream() +{ + SDL_LogOutputFunction current = nullptr; + void* current_data = nullptr; + SDL_LogGetOutputFunction(¤t, ¤t_data); + if (current == &stdioCallback) { + auto stream = static_cast(current_data); + if (stream != stdout) + fclose(stream); + } + if (current == &sdlCallback) { + auto stream = static_cast(current_data); + SDL_RWclose(stream); + } +} \ No newline at end of file diff --git a/src/logging.h b/src/logging.h index 04672208..ab3181be 100644 --- a/src/logging.h +++ b/src/logging.h @@ -43,8 +43,12 @@ class Logging : sp::NonCopyable static void setLogLevel(ELogLevel level); static void setLogFile(std::string_view filename); + static void setLogStdout(); friend const Logging& operator<<(const Logging& log, const char* str); + +private: + static void closeCurrentLogStream(); }; const Logging& operator<<(const Logging& log, const char* str); diff --git a/src/multiplayer.cpp b/src/multiplayer.cpp index 9ddd9415..0664ddcd 100644 --- a/src/multiplayer.cpp +++ b/src/multiplayer.cpp @@ -1,25 +1,40 @@ #include "multiplayer.h" #include "multiplayer_client.h" -#include "collisionable.h" #include "engine.h" #include "multiplayer_internal.h" +#include "components/multiplayer.h" -static PVector collisionable_significant; -class CollisionableReplicationData -{ -public: - glm::vec2 position{}; - glm::vec2 velocity{}; - float rotation; - float angularVelocity; - sp::Stopwatch last_update_time; - - CollisionableReplicationData() - : rotation(0), angularVelocity(0) + +namespace sp::io { + DataBuffer& operator << (DataBuffer& packet, const sp::ecs::Entity& e) { + if (game_client) { + auto si = e.getComponent(); + if (!si) + packet << sp::ecs::Entity::no_index << sp::ecs::Entity::no_index; + else + packet << si->index << si->version; + } else { + packet << e.getIndex() << e.getVersion(); + } + return packet; } -}; + DataBuffer& operator >> (DataBuffer& packet, sp::ecs::Entity& e) + { + uint32_t index, version; + packet >> index >> version; + if (game_client) { + if (index < game_client->entity_mapping.size()) + e = game_client->entity_mapping[index]; + else + e = {}; + } else { + e = sp::ecs::Entity::forced(index, version); + } + return packet; + } +} MultiplayerClassListItem* multiplayerClassListStart; @@ -62,122 +77,26 @@ template <> bool multiplayerReplicationFunctions::isChanged(void* data, return false; } -static bool collisionable_isChanged(void* data, void* prev_data_ptr) +template <> void multiplayerReplicationFunctions::sendData(void* data, sp::io::DataBuffer& packet) { - CollisionableReplicationData* rep_data = *(CollisionableReplicationData**)prev_data_ptr; - Collisionable* c = (Collisionable*)data; - - auto position = c->getPosition(); - auto velocity = c->getVelocity(); - float rotation = c->getRotation(); - float angular_velocity = c->getAngularVelocity(); - float time_after_update = rep_data->last_update_time.get(); - float significance = 0.f; - float significant_range = 1.f; - - foreach(Collisionable, sig, collisionable_significant) - { - float dist = glm::length(sig->getPosition() - position); - float s = 0.f; - if (dist < sig->multiplayer_replication_object_significant_range) - s = 1.f; - else if (dist < sig->multiplayer_replication_object_significant_range * 2.f) - s = 1.f - ((dist - sig->multiplayer_replication_object_significant_range) / sig->multiplayer_replication_object_significant_range); - - if (s > significance) - { - significance = s; - significant_range = sig->multiplayer_replication_object_significant_range; - } - } - - float delta_position = glm::length(rep_data->position - position); - float delta_velocity = glm::length(rep_data->velocity - velocity); - float delta_rotation = fabs(rep_data->rotation - rotation); - float delta_angular_velocity = fabs(rep_data->angularVelocity - angular_velocity); - - if (delta_position == 0.f && delta_velocity == 0.f && delta_rotation == 0.f && delta_angular_velocity == 0.f) - { - //If we haven't moved then the client needs no update - rep_data->last_update_time.restart(); - }else{ - float position_score = (delta_position / significant_range) * 100.f + delta_rotation * 10.f; - float velocity_score = (delta_velocity / significant_range) * 100.f + delta_angular_velocity * 10.f; - - float time_between_updates = (1.f - (position_score + velocity_score) * significance); - if (time_between_updates < 0.05f) - time_between_updates = 0.05f; - if (time_after_update > 0.5f || time_after_update > time_between_updates) - { - rep_data->last_update_time.restart(); - rep_data->position = position; - rep_data->velocity = velocity; - rep_data->rotation = rotation; - rep_data->angularVelocity = angular_velocity; - return true; - } + auto e = (sp::ecs::Entity*)data; + if (*e) { + packet << e->getIndex(); + } else { + packet << sp::ecs::Entity::no_index; } - return false; } -static void collisionable_sendFunction(void* data, sp::io::DataBuffer& packet) +template <> void multiplayerReplicationFunctions::receiveData(void* data, sp::io::DataBuffer& packet) { - Collisionable* c = (Collisionable*)data; - - auto position = c->getPosition(); - auto velocity = c->getVelocity(); - float rotation = c->getRotation(); - float angularVelocity = c->getAngularVelocity(); - - packet << position << velocity << rotation << angularVelocity; -} - -static void collisionable_receiveFunction(void* data, sp::io::DataBuffer& packet) -{ - Collisionable* c = (Collisionable*)data; - - glm::vec2 position{}; - glm::vec2 velocity{}; - float rotation; - float angularVelocity; - - packet >> position >> velocity >> rotation >> angularVelocity; - - c->setPosition(position); - c->setVelocity(velocity); - c->setRotation(rotation); - c->setAngularVelocity(angularVelocity); -} - -static void collisionable_cleanupFunction(void* prev_data_ptr) -{ - CollisionableReplicationData* rep_data = *(CollisionableReplicationData**)prev_data_ptr; - delete rep_data; -} - -void MultiplayerObject::registerCollisionableReplication(float object_significant_range) -{ - SDL_assert(!replicated); - SDL_assert(memberReplicationInfo.size() < 0xFFFF); - - MemberReplicationInfo info; - Collisionable* collisionable = dynamic_cast(this); - SDL_assert(collisionable); - collisionable->multiplayer_replication_object_significant_range = object_significant_range; - if (object_significant_range > 0) - collisionable_significant.push_back(collisionable); - info.ptr = collisionable; -#ifdef DEBUG - info.name = "Collisionable_data"; -#endif - info.prev_data = reinterpret_cast(new CollisionableReplicationData()); - info.update_delay = 0.f; - info.update_timeout = 0.f; - info.isChangedFunction = &collisionable_isChanged; - info.sendFunction = &collisionable_sendFunction; - info.receiveFunction = &collisionable_receiveFunction; - info.cleanupFunction = &collisionable_cleanupFunction; - memberReplicationInfo.push_back(info); + auto e = (sp::ecs::Entity*)data; + + uint32_t index; + packet >> index; + if (index < game_client->entity_mapping.size()) + *e = game_client->entity_mapping[index]; + else + *e = sp::ecs::Entity{}; } void MultiplayerObject::sendClientCommand(sp::io::DataBuffer& packet) diff --git a/src/multiplayer.h b/src/multiplayer.h index 42111bed..59a10a41 100644 --- a/src/multiplayer.h +++ b/src/multiplayer.h @@ -5,45 +5,47 @@ #include #include "Updatable.h" #include "stringImproved.h" +#include "ecs/entity.h" +#include "multiplayer_internal.h" class MultiplayerObject; -#define REGISTER_MULTIPLAYER_ENUM(type) \ - static inline sp::io::DataBuffer& operator << (sp::io::DataBuffer& packet, const type& e) { return packet << int8_t(e); } \ - static inline sp::io::DataBuffer& operator >> (sp::io::DataBuffer& packet, type& mw) { int8_t tmp; packet >> tmp; mw = type(tmp); return packet; } +#define REGISTER_MULTIPLAYER_CLASS(className, name) MultiplayerClassListItem MultiplayerClassListItem ## className(name, createMultiplayerObject); +namespace sp::io { + template static inline DataBuffer& operator << (DataBuffer& packet, const glm::vec<2, T, Q>& v) + { + return packet << v.x << v.y; + } + template static inline DataBuffer& operator >> (DataBuffer& packet, glm::vec<2, T, Q>& v) + { + return packet >> v.x >> v.y; + } + template static inline DataBuffer& operator << (DataBuffer& packet, const glm::vec<3, T, Q>& v) + { + return packet << v.x << v.y << v.z; + } + template static inline DataBuffer& operator >> (DataBuffer& packet, glm::vec<3, T, Q>& v) + { + return packet >> v.x >> v.y >> v.z; + } + template static inline DataBuffer& operator << (DataBuffer& packet, const std::pair& pair) + { + return packet << pair.first << pair.second; + } + template static inline DataBuffer& operator >> (DataBuffer& packet, std::pair& pair) + { + return packet >> pair.first >> pair.second; + } -#define REGISTER_MULTIPLAYER_CLASS(className, name) MultiplayerClassListItem MultiplayerClassListItem ## className(name, createMultiplayerObject); + static inline DataBuffer& operator << (DataBuffer& packet, const glm::u8vec4& c) { return packet << c.r << c.g << c.b << c.a; } \ + static inline DataBuffer& operator >> (DataBuffer& packet, glm::u8vec4& c) { packet >> c.r >> c.g >> c.b >> c.a; return packet; } -template static inline sp::io::DataBuffer& operator << (sp::io::DataBuffer& packet, const glm::vec<2, T, Q>& v) -{ - return packet << v.x << v.y; -} -template static inline sp::io::DataBuffer& operator >> (sp::io::DataBuffer& packet, glm::vec<2, T, Q>& v) -{ - return packet >> v.x >> v.y; -} -template static inline sp::io::DataBuffer& operator << (sp::io::DataBuffer& packet, const glm::vec<3, T, Q>& v) -{ - return packet << v.x << v.y << v.z; -} -template static inline sp::io::DataBuffer& operator >> (sp::io::DataBuffer& packet, glm::vec<3, T, Q>& v) -{ - return packet >> v.x >> v.y >> v.z; -} -template static inline sp::io::DataBuffer& operator << (sp::io::DataBuffer& packet, const std::pair& pair) -{ - return packet << pair.first << pair.second; -} -template static inline sp::io::DataBuffer& operator >> (sp::io::DataBuffer& packet, std::pair& pair) -{ - return packet >> pair.first >> pair.second; + DataBuffer& operator << (DataBuffer& packet, const sp::ecs::Entity& e); + DataBuffer& operator >> (DataBuffer& packet, sp::ecs::Entity& e); } -static inline sp::io::DataBuffer& operator << (sp::io::DataBuffer& packet, const glm::u8vec4& c) { return packet << c.r << c.g << c.b << c.a; } \ -static inline sp::io::DataBuffer& operator >> (sp::io::DataBuffer& packet, glm::u8vec4& c) { packet >> c.r >> c.g >> c.b >> c.a; return packet; } - template struct multiplayerReplicationFunctions { static bool isChanged(void* data, void* prev_data_ptr); @@ -124,6 +126,9 @@ bool multiplayerReplicationFunctions::isChanged(void* data, void* prev_data_p template <> bool multiplayerReplicationFunctions::isChanged(void* data, void* prev_data_ptr); +template <> void multiplayerReplicationFunctions::sendData(void* data, sp::io::DataBuffer& packet); +template <> void multiplayerReplicationFunctions::receiveData(void* data, sp::io::DataBuffer& packet); + //In between class that handles all the nasty synchronization of objects between server and client. //I'm assuming that it should be a pure virtual class though. class MultiplayerObject : public virtual PObject @@ -158,8 +163,8 @@ class MultiplayerObject : public virtual PObject bool isClient() { return !on_server; } #ifdef DEBUG -#define STRINGIFY(n) #n -#define registerMemberReplication(member, ...) registerMemberReplication_(STRINGIFY(member), member , ## __VA_ARGS__ ) +#define SP_MP_STRINGIFY(n) #n +#define registerMemberReplication(member, ...) registerMemberReplication_(SP_MP_STRINGIFY(member), member , ## __VA_ARGS__ ) #define F_PARAM const char* name, #else #define registerMemberReplication(member, ...) registerMemberReplication_(member , ## __VA_ARGS__ ) @@ -238,8 +243,6 @@ class MultiplayerObject : public virtual PObject memberReplicationInfo[n].update_timeout = 0.0f; } - void registerCollisionableReplication(float object_significant_range = -1); - int32_t getMultiplayerId() { return multiplayerObjectId; } const string& getMultiplayerClassIdentifier() { return multiplayerClassIdentifier; } void sendClientCommand(sp::io::DataBuffer& packet);//Send a command from the client to the server. diff --git a/src/multiplayer/collision.cpp b/src/multiplayer/collision.cpp new file mode 100644 index 00000000..d9e44993 --- /dev/null +++ b/src/multiplayer/collision.cpp @@ -0,0 +1,131 @@ +#include "multiplayer/collision.h" +#include "ecs/query.h" +#include "ecs/multiplayer.h" +#include "components/collision.h" +#include "engine.h" + + +namespace sp::multiplayer { + +void TransformReplication::onEntityDestroyed(uint32_t index) +{ + info.remove(index); +} + +void TransformReplication::sendAll(sp::io::DataBuffer& packet) +{ + for(auto [entity, transform] : sp::ecs::Query()) + { + packet << CMD_ECS_SET_COMPONENT << component_index << entity.getIndex(); + auto p = transform.getPosition(); + auto r = transform.getRotation(); + packet << p.x << p.y << r; + } +} + +void TransformReplication::update(sp::io::DataBuffer& packet) +{ + for(auto [index, data] : info) { + if (!sp::ecs::Entity::forced(index, data).hasComponent()) { + info.remove(index); + packet << CMD_ECS_DEL_COMPONENT << component_index << index; + } + } + for(auto [entity, transform] : sp::ecs::Query()) { + if (!info.has(entity.getIndex()) || transform.multiplayer_dirty) { + info.set(entity.getIndex(), entity.getVersion()); + packet << CMD_ECS_SET_COMPONENT << component_index << entity.getIndex(); + auto p = transform.getPosition(); + auto r = transform.getRotation(); + packet << p.x << p.y << r; + transform.multiplayer_dirty = false; + transform.last_send_position = p; + transform.last_send_rotation = r; + transform.last_send_time = engine->getElapsedTime(); + } + } +} + +void TransformReplication::receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) +{ + float x, y, r; + packet >> x >> y >> r; + auto& t = entity.getOrAddComponent(); + t.setPosition({x, y}); + t.setRotation(r); +} + +void TransformReplication::remove(sp::ecs::Entity entity) +{ + entity.removeComponent(); +} + +void PhysicsReplication::onEntityDestroyed(uint32_t index) +{ + info.remove(index); +} + +void PhysicsReplication::sendAll(sp::io::DataBuffer& packet) +{ + for(auto [entity, physics] : sp::ecs::Query()) + { + packet << CMD_ECS_SET_COMPONENT << component_index << entity.getIndex(); + packet << uint32_t(3) << physics.type << physics.shape << physics.size.x << physics.size.y; + packet << physics.linear_velocity.x << physics.linear_velocity.y << physics.angular_velocity; + } +} + +void PhysicsReplication::update(sp::io::DataBuffer& packet) +{ + for(auto [index, data] : info) { + if (!sp::ecs::Entity::forced(index, data.version).hasComponent()) { + info.remove(index); + packet << CMD_ECS_DEL_COMPONENT << component_index << index; + } + } + for(auto [entity, physics] : sp::ecs::Query()) { + if (!info.has(entity.getIndex()) || physics.multiplayer_dirty) { + info.set(entity.getIndex(), {entity.getVersion(), physics.linear_velocity, physics.angular_velocity}); + packet << CMD_ECS_SET_COMPONENT << component_index << entity.getIndex(); + packet << uint32_t(3) << physics.type << physics.shape << physics.size.x << physics.size.y; + packet << physics.linear_velocity.x << physics.linear_velocity.y << physics.angular_velocity; + physics.multiplayer_dirty = false; + } else { + auto& i = info.get(entity.getIndex()); + if (glm::length2(i.velocity - physics.linear_velocity) > 5.0f || glm::length2(i.angular_velocity - physics.angular_velocity) > 5.0f) { + info.set(entity.getIndex(), {entity.getVersion(), physics.linear_velocity, physics.angular_velocity}); + packet << CMD_ECS_SET_COMPONENT << component_index << entity.getIndex(); + packet << uint32_t(2U) << physics.linear_velocity.x << physics.linear_velocity.y << physics.angular_velocity; + } + } + } +} + +void PhysicsReplication::receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) +{ + auto [flags] = packet.read(); + auto& p = entity.getOrAddComponent(); + if (flags & 1U) { + auto [type, shape, w, h] = packet.read(); + switch(shape) { + case sp::Physics::Shape::Circle: + p.setCircle(type, w); + break; + case sp::Physics::Shape::Rectangle: + p.setRectangle(type, {w, h}); + break; + } + } + if (flags & 2U) { + auto [x, y, a] = packet.read(); + p.setVelocity({x, y}); + p.setAngularVelocity(a); + } +} + +void PhysicsReplication::remove(sp::ecs::Entity entity) +{ + entity.removeComponent(); +} + +} \ No newline at end of file diff --git a/src/multiplayer/collision.h b/src/multiplayer/collision.h new file mode 100644 index 00000000..bfd108d5 --- /dev/null +++ b/src/multiplayer/collision.h @@ -0,0 +1,37 @@ +#pragma once + +#include "ecs/multiplayer.h" + + +namespace sp::multiplayer { + +class TransformReplication : public sp::ecs::ComponentReplicationBase +{ +public: + sp::SparseSet info; + + void onEntityDestroyed(uint32_t index) override; + void sendAll(sp::io::DataBuffer& packet) override; + void update(sp::io::DataBuffer& packet) override; + void receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) override; + void remove(sp::ecs::Entity entity) override; +}; + +class PhysicsReplication : public sp::ecs::ComponentReplicationBase +{ +public: + struct Info { + uint32_t version; + glm::vec2 velocity; + float angular_velocity; + }; + sp::SparseSet info; + + void onEntityDestroyed(uint32_t index) override; + void sendAll(sp::io::DataBuffer& packet) override; + void update(sp::io::DataBuffer& packet) override; + void receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) override; + void remove(sp::ecs::Entity entity) override; +}; + +} \ No newline at end of file diff --git a/src/multiplayer_client.cpp b/src/multiplayer_client.cpp index f530698b..982fc049 100644 --- a/src/multiplayer_client.cpp +++ b/src/multiplayer_client.cpp @@ -2,7 +2,9 @@ #include "multiplayer.h" #include "multiplayer_internal.h" #include "engine.h" +#include "components/multiplayer.h" +#include "ecs/multiplayer.h" #include "io/network/tcpSocket.h" #ifdef STEAMSDK #include "io/network/steamP2PSocket.h" @@ -248,6 +250,62 @@ void GameClient::update(float /*delta*/) reply << CMD_ALIVE_RESP; socket->send(reply); break; + case CMD_ECS_UPDATE: + while(packet.available()) + { + uint8_t ecs_cmd; + packet >> ecs_cmd; + switch(ecs_cmd) + { + case CMD_ECS_ENTITY_CREATE: + { + uint32_t index, version; + packet >> index >> version; + if (index >= entity_mapping.size()) + entity_mapping.resize(index + 1); + entity_mapping[index] = sp::ecs::Entity::create(); + entity_mapping[index].addComponent(index, version); + } + break; + case CMD_ECS_ENTITY_DESTROY: + { + uint32_t index; + packet >> index; + if (index < entity_mapping.size()) + entity_mapping[index].destroy(); + } + break; + case CMD_ECS_SET_COMPONENT: + { + uint16_t component_index; + uint32_t index; + packet >> component_index >> index; + if (component_index < sp::ecs::MultiplayerReplication::list.size()) { + if (index < entity_mapping.size() && entity_mapping[index]) { + sp::ecs::MultiplayerReplication::list[component_index]->receive(entity_mapping[index], packet); + } else { + LOG(Error, "MP: ECS set component of unknown entity: ", index); + } + } else { + LOG(Error, "MP: ECS set component of unknown component index: ", component_index); + } + } + break; + case CMD_ECS_DEL_COMPONENT: + { + uint16_t component_index; + uint32_t index; + packet >> component_index >> index; + if (component_index < sp::ecs::MultiplayerReplication::list.size()) + if (index < entity_mapping.size() && entity_mapping[index]) + sp::ecs::MultiplayerReplication::list[component_index]->remove(entity_mapping[index]); + } + break; + default: + LOG(Error, "Unknown ECS command in packet?..."); + } + } + break; default: LOG(ERROR) << "Unknown command from server: " << command; } diff --git a/src/multiplayer_client.h b/src/multiplayer_client.h index c2e85c1c..188d61a3 100644 --- a/src/multiplayer_client.h +++ b/src/multiplayer_client.h @@ -6,6 +6,7 @@ #include "multiplayer_server.h" #include "networkAudioStream.h" #include "timer.h" +#include "ecs/entity.h" #include #include @@ -20,6 +21,8 @@ class GameClient : public Updatable { constexpr static float no_data_disconnect_time = 20; public: + std::vector entity_mapping; + enum Status { Connecting, diff --git a/src/multiplayer_internal.h b/src/multiplayer_internal.h index b935fb10..a5d700b4 100644 --- a/src/multiplayer_internal.h +++ b/src/multiplayer_internal.h @@ -25,4 +25,10 @@ static const command_t CMD_AUDIO_COMM_START = 0x0020; static const command_t CMD_AUDIO_COMM_DATA = 0x0021; static const command_t CMD_AUDIO_COMM_STOP = 0x0022; +static constexpr command_t CMD_ECS_UPDATE = 0x0030; +static constexpr uint8_t CMD_ECS_ENTITY_CREATE = 0x00; +static constexpr uint8_t CMD_ECS_ENTITY_DESTROY = 0x01; +static constexpr uint8_t CMD_ECS_SET_COMPONENT = 0x02; +static constexpr uint8_t CMD_ECS_DEL_COMPONENT = 0x03; + #endif//MULTIPLAYER_INTERNAL_H diff --git a/src/multiplayer_server.cpp b/src/multiplayer_server.cpp index 3c9245ee..8beb9912 100644 --- a/src/multiplayer_server.cpp +++ b/src/multiplayer_server.cpp @@ -3,6 +3,8 @@ #include "multiplayer_internal.h" #include "multiplayer.h" #include "engine.h" +#include "ecs/entity.h" +#include "ecs/multiplayer.h" #include "io/http/request.h" @@ -16,7 +18,7 @@ #if MULTIPLAYER_COLLECT_DATA_STATS sp::SystemTimer multiplayer_stats_dump; static std::unordered_map multiplayer_stats; -#define ADD_MULTIPLAYER_STATS(name, bytes) multiplayer_stats[name] += (bytes) +#define ADD_MULTIPLAYER_STATS(name, bytes) do { if (bytes) { multiplayer_stats[name] += (bytes); } } while(0) #else #define ADD_MULTIPLAYER_STATS(name, bytes) do {} while(0) #endif @@ -129,6 +131,9 @@ void GameServer::update(float /*gameDelta*/) { sp::SystemStopwatch update_run_time_clock; //Clock used to measure how much time this update cycle is costing us. + if (last_update_time.get() < 1.0f / 60.0f) { + return; // Only update 60 times per second even if the game runs at higher FPS. + } //Calculate our own delta, as we want wall-time delta, the gameDelta can be modified by the current game speed (could even be 0 on pause) float delta = last_update_time.restart(); @@ -143,6 +148,44 @@ void GameServer::update(float /*gameDelta*/) sendAll(packet); } + //Replicate ECS data, we send this as one big packet so ECS state is always consistent on the client. + sp::io::DataBuffer ecs_packet; + ecs_packet << CMD_ECS_UPDATE; + auto empty_ecs_packet_size = ecs_packet.getDataSize(); +#if MULTIPLAYER_COLLECT_DATA_STATS + auto ecs_overhead_size = empty_ecs_packet_size; +#endif + // For each entity, check which version number we last transmitted and if it is changed, transmit creation/deletion of entities. + ecs_entity_version.resize(sp::ecs::Entity::entity_version.size(), std::numeric_limits::max()); + for(uint32_t index=0; indexonEntityDestroyed(index); + } + } + ecs_entity_version[index] = sp::ecs::Entity::entity_version[index]; + if (!(ecs_entity_version[index] & sp::ecs::Entity::destroyed_flag)) + ecs_packet << CMD_ECS_ENTITY_CREATE << index << ecs_entity_version[index]; + } + } + // For each component type, check which components are added/changed/deleted and send that over. + for(auto& ecsrb : sp::ecs::MultiplayerReplication::list) { +#if MULTIPLAYER_COLLECT_DATA_STATS + auto pre_size = ecs_packet.getDataSize(); +#endif + ecsrb->update(ecs_packet); +#if MULTIPLAYER_COLLECT_DATA_STATS + ADD_MULTIPLAYER_STATS("ECS:UPDATE:" + string(typeid(*ecsrb).name()), ecs_packet.getDataSize() - pre_size); + ecs_overhead_size += ecs_packet.getDataSize() - pre_size; +#endif + } + if (ecs_packet.getDataSize() > empty_ecs_packet_size) { + sendAll(ecs_packet); + ADD_MULTIPLAYER_STATS("ECS:OVERHEAD", ecs_packet.getDataSize() - ecs_overhead_size); + } + std::vector delList; for(std::unordered_map >::iterator i=objectMap.begin(); i != objectMap.end(); i++) { @@ -452,6 +495,20 @@ void GameServer::handleNewClient(ClientInfo& info) onNewClient(info.client_id); + //Replicate ECS data, we send this as one big packet so ECS state is always consistent on the client. + sp::io::DataBuffer ecs_packet; + ecs_packet << CMD_ECS_UPDATE; + // For each entity, check which version number we last transmitted and if it is changed, transmit creation/deletion of entities. + for(uint32_t index=0; indexsendAll(ecs_packet); + sendDataCounter += ecs_packet.getDataSize(); + info.socket->queue(ecs_packet); + //On a new client, first create all the already existing objects. And update all the values. for(std::unordered_map >::iterator i=objectMap.begin(); i != objectMap.end(); i++) { diff --git a/src/multiplayer_server.h b/src/multiplayer_server.h index 988db30b..aa96c728 100644 --- a/src/multiplayer_server.h +++ b/src/multiplayer_server.h @@ -88,6 +88,8 @@ class GameServer : public Updatable int32_t nextObjectId; std::unordered_map > objectMap; + std::vector ecs_entity_version; + string master_server_url; std::thread master_server_update_thread; MasterServerState master_server_state = MasterServerState::Disabled; diff --git a/src/result.h b/src/result.h new file mode 100644 index 00000000..101c6e68 --- /dev/null +++ b/src/result.h @@ -0,0 +1,97 @@ +#ifndef SP2_RESULT_H +#define SP2_RESULT_H + +#include "logging.h" + +namespace sp { + +template class Result +{ +public: + Result(T&& value) + : success(true), ok_value(std::forward(value)) + { + } + + bool isOk() const + { + return success; + } + + bool isErr() const + { + return !success; + } + + const T& value() + { + if (!success) + { + LOG(Error, err_value); + success = true; + } + return ok_value; + } + + const string& error() const + { + return err_value; + } + + static Result makeError(string&& error) + { + return Result(std::forward(error), {}); + } +private: + class ErrorConstructor{}; + + Result(string&& error, const ErrorConstructor&) + : success(false), err_value(std::forward(error)) + { + } + + bool success; + T ok_value{}; + string err_value; +}; +template<> class Result { +public: + Result() + : success(true) + { + } + + bool isOk() const + { + return success; + } + + bool isErr() const + { + return !success; + } + + const string& error() const + { + return err_value; + } + + static Result makeError(string&& error) + { + return Result(std::forward(error), {}); + } +private: + class ErrorConstructor{}; + + Result(string&& error, const ErrorConstructor&) + : success(false), err_value(std::forward(error)) + { + } + + bool success; + string err_value; +}; + +} + +#endif//SP2_RESULT_H \ No newline at end of file diff --git a/src/script/callback.cpp b/src/script/callback.cpp new file mode 100644 index 00000000..a0b4edd6 --- /dev/null +++ b/src/script/callback.cpp @@ -0,0 +1,39 @@ +#include "callback.h" +#include "environment.h" + +namespace sp::script { + +Callback::Callback() +{ +} + +Callback::Callback(const Callback& other) +{ + lua_rawgetp(Environment::L, LUA_REGISTRYINDEX, &other); + lua_rawsetp(Environment::L, LUA_REGISTRYINDEX, this); +} + +Callback::~Callback() +{ + lua_pushnil(Environment::L); + lua_rawsetp(Environment::L, LUA_REGISTRYINDEX, this); +} + +Callback& Callback::operator=(const Callback& other) +{ + lua_rawgetp(Environment::L, LUA_REGISTRYINDEX, &other); + lua_rawsetp(Environment::L, LUA_REGISTRYINDEX, this); + return *this; +} + +Callback::operator bool() +{ + lua_rawgetp(Environment::L, LUA_REGISTRYINDEX, this); + if (!lua_isfunction(Environment::L, -1)) { + lua_pop(Environment::L, 1); + return false; + } + return true; +} + +}; diff --git a/src/script/callback.h b/src/script/callback.h new file mode 100644 index 00000000..ffc78143 --- /dev/null +++ b/src/script/callback.h @@ -0,0 +1,85 @@ +#ifndef SP_SCRIPT_CALLBACK +#define SP_SCRIPT_CALLBACK + +#include "result.h" +#include "script/conversion.h" +#include "script/environment.h" + + +namespace sp::script { + +class Callback +{ +public: + Callback(); + Callback(const Callback& other); + ~Callback(); + + Callback& operator=(const Callback& other); + + explicit operator bool(); + + template Result call(ARGS... args) { + //Get this callback from the registry + lua_rawgetp(Environment::L, LUA_REGISTRYINDEX, this); + if (!lua_isfunction(Environment::L, -1)) { + lua_pop(Environment::L, 1); + return Result::makeError("Callback not set."); + } + //If it exists, push the arguments with it, can run it. + int arg_count = (Convert::toLua(Environment::L, args) + ... + 0); + int result = lua_pcall(Environment::L, arg_count, 1, 0); + if (result) { + auto ret = Result::makeError(lua_tostring(Environment::L, -1)); + lua_pop(Environment::L, 1); + return ret; + } + if constexpr (!std::is_void_v) { + auto return_value = Convert::fromLua(Environment::L, -1); + lua_pop(Environment::L, 1); + return return_value; + } else { + return {}; + } + } + + // Set a global in the environment of this callback. You generally do not want this. + // But I need it for backwards compatibility. + template void setGlobal(const char* key, const T& value) { + lua_rawgetp(Environment::L, LUA_REGISTRYINDEX, this); + if (!lua_isfunction(Environment::L, -1)) { + lua_pop(Environment::L, 1); + return; + } + lua_getupvalue(Environment::L, -1, 1); + if (!lua_istable(Environment::L, -1)) { + lua_pop(Environment::L, 2); + return; + } + if (Convert::toLua(Environment::L, value) < 1) { + lua_pop(Environment::L, 2); + return; + } + lua_setfield(Environment::L, -2, key); + lua_pop(Environment::L, 2); + } +}; + +template<> struct Convert { + static int toLua(lua_State* L, const Callback& callback) { + lua_rawgetp(L, LUA_REGISTRYINDEX, &callback); + return 1; + } + static Callback fromLua(lua_State* L, int idx) { + Callback result; + if (!lua_isnil(L, idx)) + luaL_checktype(L, idx, LUA_TFUNCTION); + lua_pushvalue(L, idx); + lua_rawsetp(L, LUA_REGISTRYINDEX, &result); + return result; + } +}; + +} + +#endif//SP_SCRIPT_ENVIRONMENT \ No newline at end of file diff --git a/src/script/component.h b/src/script/component.h new file mode 100644 index 00000000..43376798 --- /dev/null +++ b/src/script/component.h @@ -0,0 +1,239 @@ +#ifndef SP_SCRIPT_COMPONENT +#define SP_SCRIPT_COMPONENT + +#include "conversion.h" +#include "environment.h" +#include "string.h" +#include "ecs/query.h" +#include + + +namespace sp::script { + +class ComponentRegistry +{ +public: + using FuncPtr = int(*)(lua_State*, sp::ecs::Entity, const char*); + FuncPtr getter; + FuncPtr setter; + using QueryFuncPtr = int(*)(lua_State*); + QueryFuncPtr query; + + static std::unordered_map components; +}; + +namespace detail { + struct MemberData { + using GetterPtr = int(*)(lua_State*, const void*); + using SetterPtr = void(*)(lua_State*, void*); + + GetterPtr getter; + SetterPtr setter; + }; + struct IndexedMemberData { + using GetterPtr = int(*)(lua_State*, const void*, int index); + using SetterPtr = void(*)(lua_State*, void*, int index); + + GetterPtr getter; + SetterPtr setter; + }; +} + +template class ComponentHandler +{ +public: + static void name(const char* name) { + component_name = name; + ComponentRegistry::components[name] = {luaComponentGetter, luaComponentSetter, luaComponentQuery}; + + auto L = Environment::getLuaState(); + luaL_newmetatable(L, name); + lua_pushcfunction(L, luaIndex); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, luaNewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, [](lua_State* L) { + if (!array_count_func) luaL_error(L, "Tried to get length of component %s that has no array", component_name); + auto ptr = luaToComponent(L, -1); + if (!ptr) return 0; + lua_pushinteger(L, array_count_func(*ptr)); + return 1; + }); + lua_setfield(L, -2, "__len"); + lua_pushstring(L, "sandboxed"); + lua_setfield(L, -2, "__metatable"); + lua_pop(L, 1); + + array_metatable_name = name + string("_array"); + luaL_newmetatable(L, array_metatable_name.c_str()); + lua_pushcfunction(L, [](lua_State* L) { + IndexedComponent* icptr = static_cast(lua_touserdata(L, -2)); + if (!icptr) return 0; + if (!icptr->entity) return 0; + auto ptr = icptr->entity.template getComponent(); + if (!ptr) return 0; + if (array_count_func(*ptr) <= icptr->index) return 0; + auto key = luaL_checkstring(L, -1); + auto it = indexed_members.find(key); + if (it == indexed_members.end()) return 0; + return it->second.getter(L, ptr, icptr->index); + }); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, [](lua_State* L) { + IndexedComponent* icptr = static_cast(lua_touserdata(L, -3)); + if (!icptr) return 0; + if (!icptr->entity) return 0; + auto ptr = icptr->entity.template getComponent(); + if (!ptr) return 0; + if (array_count_func(*ptr) <= icptr->index) return luaL_error(L, "Index out of range for assignment on component %s", component_name); + auto key = luaL_checkstring(L, -2); + auto it = indexed_members.find(key); + if (it == indexed_members.end()) return luaL_error(L, "Trying to set unknown component %s member %s", component_name, key); + it->second.setter(L, ptr, icptr->index); + return 0; + }); + lua_setfield(L, -2, "__newindex"); + lua_pushstring(L, "sandboxed"); + lua_setfield(L, -2, "__metatable"); + lua_pop(L, 1); + } + + static inline std::unordered_map members; + static inline std::unordered_map indexed_members; + using ArrayCountPtr = int(*)(const T&); + static inline ArrayCountPtr array_count_func = nullptr; + using ArrayResizePtr = void(*)(T&, int size); + static inline ArrayResizePtr array_resize_func = nullptr; + +private: + static int luaIndex(lua_State* L) { + auto eptr = lua_touserdata(L, -2); + if (!eptr) return 0; + auto e = *static_cast(eptr); + if (!e) return 0; + auto ptr = e.getComponent(); + if (!ptr) return 0; + if (array_count_func && lua_isinteger(L, -1)) { + int index = lua_tointeger(L, -1); + if (index < 1 || index > array_count_func(*ptr)) + return 0; + auto ic = static_cast(lua_newuserdata(L, sizeof(IndexedComponent))); + ic->entity = e; + ic->index = index - 1; + luaL_getmetatable(L, array_metatable_name.c_str()); + lua_setmetatable(L, -2); + return 1; + } + auto key = luaL_checkstring(L, -1); + auto it = members.find(key); + if (it == members.end()) return 0; + return it->second.getter(L, ptr); + } + + static int luaNewIndex(lua_State* L) { + auto ptr = luaToComponent(L, -3); + if (!ptr) return 0; + if (array_count_func && lua_isinteger(L, -2)) { + int index = lua_tointeger(L, -2); + if (index < 1) + return 0; + if (lua_isnil(L, -1)) { + array_resize_func(*ptr, index - 1); + return 0; + } else if (lua_istable(L, -1)) { + if (index > array_count_func(*ptr)) + array_resize_func(*ptr, index); + lua_pushnil(L); + while(lua_next(L, -2)) { + auto key = luaL_checkstring(L, -2); + auto it = indexed_members.find(key); + if (it == indexed_members.end()) return luaL_error(L, "Trying to set unknown component %s member %s", component_name, key); + it->second.setter(L, ptr, index - 1); + lua_pop(L, 1); + } + } else { + return luaL_error(L, "Bad assignment to component %s, nil or table expected.", component_name); + } + return 0; + } + auto key = luaL_checkstring(L, -2); + auto it = members.find(key); + if (it == members.end()) return luaL_error(L, "Trying to set unknown component %s member %s", component_name, key); + it->second.setter(L, ptr); + return 0; + } + + static T* luaToComponent(lua_State* L, int index) { + auto eptr = lua_touserdata(L, index); + if (!eptr) return nullptr; + auto e = *static_cast(eptr); + if (!e) return nullptr; + return e.getComponent(); + } + + static int luaComponentGetter(lua_State* L, sp::ecs::Entity e, const char* key) { + if (!e.hasComponent()) return 0; + *static_cast(lua_newuserdata(L, sizeof(ecs::Entity))) = e; + luaL_getmetatable(L, key); + lua_setmetatable(L, -2); + return 1; + } + + static int luaComponentSetter(lua_State* L, sp::ecs::Entity e, const char* key) { + if (lua_isnil(L, -1)) { + e.removeComponent(); + } else if (lua_istable(L, -1)) { + auto& component = e.addComponent(); + lua_pushnil(L); + while(lua_next(L, -2)) { + if (array_count_func && lua_isinteger(L, -2)) { + int index = lua_tointeger(L, -2) - 1; + if (index < 0) luaL_error(L, "Cannot assign indexes below 1 on component %s", component_name); + if (array_count_func(component) < index + 1) + array_resize_func(component, index + 1); + luaL_checktype(L, -1, LUA_TTABLE); + lua_pushnil(L); + while(lua_next(L, -2)) { + auto key = luaL_checkstring(L, -2); + auto it = indexed_members.find(key); + if (it == indexed_members.end()) return luaL_error(L, "Trying to set unknown component %s member %s", component_name, key); + it->second.setter(L, &component, index); + lua_pop(L, 1); + } + } else { + auto key = luaL_checkstring(L, -2); + auto it = members.find(key); + if (it == members.end()) return luaL_error(L, "Trying to set unknown component %s member %s", component_name, key); + it->second.setter(L, &component); + } + lua_pop(L, 1); + } + } else { + return luaL_error(L, "Bad assignment to component %s member %s, nil or table expected.", component_name, key); + } + return 0; + } + + static int luaComponentQuery(lua_State* L) + { + lua_newtable(L); + int index = 1; + for(auto [e, comp] : sp::ecs::Query()) { + Convert::toLua(L, e); + lua_rawseti(L, -2, index); + index++; + } + return 1; + } + + static inline const char* component_name; + static inline string array_metatable_name; + struct IndexedComponent { + sp::ecs::Entity entity; + int index; + }; +}; + +} + +#endif//SP_SCRIPT_ENVIRONMENT \ No newline at end of file diff --git a/src/script/conversion.h b/src/script/conversion.h new file mode 100644 index 00000000..beebdfc7 --- /dev/null +++ b/src/script/conversion.h @@ -0,0 +1,108 @@ +#ifndef SP_SCRIPT_CONVERSION +#define SP_SCRIPT_CONVERSION + +#include "stringImproved.h" +#include "ecs/entity.h" +#include "lua/lua.hpp" +#include + + +namespace sp::script { + +// Special class to capture all results of a run/call as a list of strings. +class CaptureAllResults +{ +public: + std::vector result; +}; + +template struct Convert {}; +template<> struct Convert { + static int toLua(lua_State* L, bool value) { lua_pushboolean(L, value); return 1; } + static bool fromLua(lua_State* L, int idx) { return lua_toboolean(L, idx); } +}; +template<> struct Convert { + static int toLua(lua_State* L, int value) { lua_pushinteger(L, value); return 1; } + static int fromLua(lua_State* L, int idx) { return lua_tointeger(L, idx); } +}; +template<> struct Convert { + static int toLua(lua_State* L, uint32_t value) { lua_pushinteger(L, value); return 1; } + static int fromLua(lua_State* L, uint32_t idx) { return lua_tointeger(L, idx); } +}; +template<> struct Convert { + static int toLua(lua_State* L, float value) { lua_pushnumber(L, double(value)); return 1; } + static float fromLua(lua_State* L, int idx) { return lua_tonumber(L, idx); } +}; +template<> struct Convert { + static int toLua(lua_State* L, double value) { lua_pushnumber(L, value); return 1; } + static double fromLua(lua_State* L, int idx) { return lua_tonumber(L, idx); } +}; +template<> struct Convert { + static int toLua(lua_State* L, const string& value) { lua_pushstring(L, value.c_str()); return 1; } + static string fromLua(lua_State* L, int idx) { auto s = lua_tostring(L, idx); if (s) return s; return ""; } +}; +template<> struct Convert { + static int toLua(lua_State* L, lua_CFunction value) { lua_pushcfunction(L, value); return 1; } +}; +template<> struct Convert { + static int toLua(lua_State* L, ecs::Entity value) { + static_assert(sizeof(void*) == sizeof(ecs::Entity)); + union { void* ptr; ecs::Entity e; }u{}; + u.e = value; + lua_pushlightuserdata(L, u.ptr); + return 1; + } + static ecs::Entity fromLua(lua_State* L, int idx) { + static_assert(sizeof(void*) == sizeof(ecs::Entity)); + if (!lua_islightuserdata(L, idx)) + return {}; + union { void* ptr; ecs::Entity e; }u{}; + u.ptr = lua_touserdata(L, idx); + return u.e; + } +}; + +template struct Convert> { + static int toLua(lua_State* L, std::optional value) { if (value.has_value()) return Convert::toLua(L, value.value()); lua_pushnil(L); return 1; } + static std::optional fromLua(lua_State* L, int idx) { if (idx <= lua_gettop(L) && !lua_isnil(L, idx)) return Convert::fromLua(L, idx); return {}; } +}; + +template<> struct Convert { + static CaptureAllResults fromLua(lua_State* L, int idx) { + CaptureAllResults res; + int nres = lua_gettop(L); + for(int n=2; n<=nres; n++) { + auto s = luaL_tolstring(L, n, nullptr); + lua_pop(L, 1); + res.result.push_back(s); + } + return res; + } +}; + +template struct Convert { + using FT = RET(*)(ARGS...); + static int toLua(lua_State* L, FT value) { + auto f = reinterpret_cast(lua_newuserdata(L, sizeof(FT))); + *f = value; + lua_pushcclosure(L, [](lua_State* L) -> int { + auto f = *reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); + return callHelper(L, f, std::make_index_sequence()); + }, 1); + return 1; + } +private: + template static int callHelper(lua_State* L, FT f, std::index_sequence) { + if constexpr (!std::is_void_v) { + auto res = f(Convert::fromLua(L, N + 1)...); + return Convert::toLua(L, res); + } else { + f(Convert::fromLua(L, N + 1)...); + return 0; + } + } +}; + +} + +#endif//SP_SCRIPT_ENVIRONMENT \ No newline at end of file diff --git a/src/script/environment.cpp b/src/script/environment.cpp new file mode 100644 index 00000000..5e58484e --- /dev/null +++ b/src/script/environment.cpp @@ -0,0 +1,245 @@ +#include "script/environment.h" +#include "script/component.h" +#include + + +namespace sp::script { + +std::unordered_map ComponentRegistry::components; + +class LuaTableComponent { +public: + LuaTableComponent() { + lua_newtable(Environment::L); + lua_rawsetp(Environment::L, LUA_REGISTRYINDEX, this); + } + ~LuaTableComponent() { + lua_pushnil(Environment::L); + lua_rawsetp(Environment::L, LUA_REGISTRYINDEX, this); + } + LuaTableComponent(const LuaTableComponent& other) { + lua_rawgetp(Environment::L, LUA_REGISTRYINDEX, &other); + lua_rawsetp(Environment::L, LUA_REGISTRYINDEX, this); + } + LuaTableComponent& operator=(const LuaTableComponent& other) { + if (this == &other) + return *this; + lua_rawgetp(Environment::L, LUA_REGISTRYINDEX, &other); + lua_rawsetp(Environment::L, LUA_REGISTRYINDEX, this); + return *this; + } +}; + + +lua_State* Environment::L = nullptr; + +Environment::Environment(Environment* parent) +{ + getLuaState(); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "_G"); + + for(auto s : { + "assert", "error", "getmetatable", "ipairs", "next", "pairs", "pcall", "rawequal", "rawlen", "rawget", "rawset", "select", "setmetatable", "tonumber", "tostring", "xpcall", "type", "_VERSION", + "table", "string", "math" + }) { + lua_getglobal(L, s); + lua_setfield(L, -2, s); + } + + lua_newtable(L); /* meta table for the environment, with an __index pointing to the parent environment so we can access it's data. */ + if (parent) { + lua_pushstring(L, "__index"); + lua_rawgetp(L, LUA_REGISTRYINDEX, parent); + lua_rawset(L, -3); + } + lua_pushstring(L, "sandbox"); + lua_setfield(L, -2, "__metatable"); + lua_setmetatable(L, -2); + + lua_rawsetp(L, LUA_REGISTRYINDEX, this); +} + +static int luaEntityIsValid(lua_State* L) { + auto e = Convert::fromLua(L, 1); + lua_pushboolean(L, static_cast(e)); + return 1; +} + +static int luaEntityDestroy(lua_State* L) { + auto e = Convert::fromLua(L, 1); + e.destroy(); + return 0; +} + +static int luaEntityIndex(lua_State* L) { + auto key = luaL_checkstring(L, -1); + auto e = Convert::fromLua(L, -2); + if (strcmp(key, "valid") == 0) { + lua_pushboolean(L, static_cast(e)); + return 1; + } + if (strcmp(key, "components") == 0) { + *static_cast(lua_newuserdata(L, sizeof(ecs::Entity))) = e; + luaL_getmetatable(L, "entity_components"); + lua_setmetatable(L, -2); + return 1; + } + if (key[0] != '_' && luaL_getmetafield(L, -2, key) != LUA_TNIL) { + return 1; + } + if (!e) return 0; + //Check if this a value in the entityFunctionTable + lua_getfield(L, LUA_REGISTRYINDEX, "EFT"); + lua_getfield(L, -1, key); + if (!lua_isnil(L, -1)) { + return 1; + } + lua_pop(L, 2); + + //Get a value from the LTC + auto ltc = e.getComponent(); + if (!ltc) return 0; + lua_rawgetp(L, LUA_REGISTRYINDEX, ltc); + lua_pushvalue(L, -2); + lua_rawget(L, -2); + return 1; +} + +static int luaEntityNewIndex(lua_State* L) { + auto e = Convert::fromLua(L, -3); + if (!e) return 0; + + auto key = luaL_checkstring(L, -2); + if (strcmp("components", key) == 0) { + if (!lua_istable(L, -1)) + return luaL_error(L, "Assigning to components requires a table"); + + lua_pushnil(L); + while(lua_next(L, -2)) { + auto component_key = luaL_checkstring(L, -2); + auto it = ComponentRegistry::components.find(component_key); + if (it == ComponentRegistry::components.end()) + return luaL_error(L, "Tried to set non-exsisting component %s", component_key); + it->second.setter(L, e, key); + lua_pop(L, 1); + } + return 0; + } + + // Store this value in the LTC. + auto& ltc = e.getOrAddComponent(); + lua_rawgetp(L, LUA_REGISTRYINDEX, <c); + lua_pushvalue(L, -3); + lua_pushvalue(L, -3); + lua_rawset(L, -3); + return 0; +} + +static int luaEntityComponentsIndex(lua_State* L) { + auto eptr = lua_touserdata(L, -2); + if (!eptr) return 0; + auto e = *static_cast(eptr); + if (!e) return 0; + + auto key = luaL_checkstring(L, -1); + auto it = ComponentRegistry::components.find(key); + if (it != ComponentRegistry::components.end()) { + return it->second.getter(L, e, key); + } + return 0; +} + +static int luaEntityComponentsNewIndex(lua_State* L) { + auto eptr = lua_touserdata(L, -3); + if (!eptr) return 0; + auto e = *static_cast(eptr); + if (!e) return 0; + + auto key = luaL_checkstring(L, -2); + auto it = ComponentRegistry::components.find(key); + if (it != ComponentRegistry::components.end()) { + return it->second.setter(L, e, key); + } + return luaL_error(L, "Tried to set non-exsisting component %s", key); +} + +static int luaEntityEqual(lua_State* L) { + auto e1 = Convert::fromLua(L, -2); + if (!e1) return 0; + auto e2 = Convert::fromLua(L, -1); + if (!e2) return 0; + + lua_pushboolean(L, e1 == e2); + return 1; +} + +lua_State* Environment::getLuaState() +{ + if (!L) { + L = luaL_newstate(); + + luaL_requiref(L, "_G", luaopen_base, 1); + lua_pop(L, 1); + luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1); + lua_pop(L, 1); + luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1); + lua_pop(L, 1); + luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1); + lua_pop(L, 1); + + //Protect the string metatable + lua_pushliteral(L, ""); + lua_getmetatable(L, -1); + lua_pushstring(L, "sandboxed"); + lua_setfield(L, -2, "__metatable"); + lua_pop(L, 2); + + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, "EFT");//Entity function table. + + lua_pushlightuserdata(L, nullptr); // Push a "null" entity to set the metatable on + luaL_newmetatable(L, "entity"); + lua_pushcfunction(L, luaEntityIsValid); + lua_setfield(L, -2, "isValid"); + lua_pushcfunction(L, luaEntityDestroy); + lua_setfield(L, -2, "destroy"); + lua_pushcfunction(L, luaEntityIndex); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, luaEntityNewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, luaEntityEqual); + lua_setfield(L, -2, "__eq"); + lua_pushstring(L, "sandboxed"); + lua_setfield(L, -2, "__metatable"); + lua_setmetatable(L, -2); // Set the metatable on lightuserdata, which applies for all lightuserdata + lua_pop(L, 1); + + luaL_newmetatable(L, "entity_components"); + lua_pushcfunction(L, luaEntityComponentsIndex); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, luaEntityComponentsNewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushstring(L, "sandboxed"); + lua_setfield(L, -2, "__metatable"); + lua_pop(L, 1); + } + return L; +} + +Environment::~Environment() +{ + lua_pushnil(L); + lua_rawsetp(L, LUA_REGISTRYINDEX, this); +} + +int luaErrorHandler(lua_State* L) +{ + const char * msg = lua_tostring(L, -1); + luaL_traceback(L, L, msg, 2); + lua_remove(L, -2); + return 1; +} + +} diff --git a/src/script/environment.h b/src/script/environment.h new file mode 100644 index 00000000..9d864c7e --- /dev/null +++ b/src/script/environment.h @@ -0,0 +1,130 @@ +#ifndef SP_SCRIPT_ENVIRONMENT +#define SP_SCRIPT_ENVIRONMENT + +#include "stringImproved.h" +#include "script/conversion.h" +#include "result.h" +#include "resources.h" +#include + + +namespace sp::script { + +int luaErrorHandler(lua_State* L); + +class Environment : NonCopyable +{ +public: + Environment(Environment* parent=nullptr); + ~Environment(); + + void setGlobalFuncWithEnvUpvalue(const string& name, lua_CFunction f) { + //Get the environment table from the registry. + lua_rawgetp(L, LUA_REGISTRYINDEX, this); + lua_pushvalue(L, -1); + lua_pushcclosure(L, f, 1); + lua_setfield(L, -2, name.c_str()); + lua_pop(L, 1); + } + + template void setGlobal(const string& name, const T& value) { + //Get the environment table from the registry. + lua_rawgetp(L, LUA_REGISTRYINDEX, this); + if (Convert::toLua(L, value) != 1) + luaL_error(L, "Trying to set global to a type that is not a single value"); + lua_setfield(L, -2, name.c_str()); + lua_pop(L, 1); + } + + template Result runFile(const string& filename) + { + auto stream = getResourceStream(filename); + if (!stream) + return Result::makeError("Script file not found: " + filename); + auto code = stream->readAll(); + stream->destroy(); + stream = nullptr; + return runImpl(code, "@" + filename); + } + + template Result run(const string& code) { + return runImpl(code, "=[string]"); + } + + template Result call(const string& function_name, const ARGS&... args) { + lua_pushcfunction(L, luaErrorHandler); + //Try to find our function in the environment table + lua_rawgetp(L, LUA_REGISTRYINDEX, this); + lua_getfield(L, -1, function_name.c_str()); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 3); + return Result::makeError("Not a function"); + } + + int arg_count = (Convert::toLua(Environment::L, args) + ... + 0); + auto result = lua_pcall(L, arg_count, 1, -arg_count - 3); + if (result) + { + auto result = Result::makeError(lua_tostring(L, -1)); + lua_pop(L, 3); + return result; + } + + if constexpr (!std::is_void_v) { + auto return_value = Convert::fromLua(L, -1); + lua_pop(L, 3); + return return_value; + } else { + lua_pop(L, 3); + return {}; + } + } + +private: + template Result runImpl(const string& code, const string& name="=[string]") { + int stack_size = lua_gettop(L); + lua_pushcfunction(L, luaErrorHandler); + int result = luaL_loadbufferx(L, code.c_str(), code.length(), name.c_str(), "t"); + if (result) { + auto res = Result::makeError(luaL_checkstring(L, -1)); + lua_settop(L, stack_size); + return res; + } + + //Get the environment table from the registry. + lua_rawgetp(L, LUA_REGISTRYINDEX, this); + //set the environment table it as 1st upvalue + lua_setupvalue(L, -2, 1); + + int result_count = 1; + if constexpr (std::is_same_v) { + result_count = LUA_MULTRET; + } + result = lua_pcall(L, 0, result_count, -2); + if (result) { + auto result = Result::makeError(lua_tostring(L, -1)); + lua_settop(L, stack_size); + return result; + } + + if constexpr (!std::is_void_v) { + auto return_value = Convert::fromLua(L, -1); + lua_settop(L, stack_size); + return return_value; + } else { + lua_settop(L, stack_size); + return {}; + } + } + + static lua_State* getLuaState(); + static lua_State* L; + + template friend class ComponentHandler; + friend class LuaTableComponent; + friend class Callback; +}; + +} + +#endif//SP_SCRIPT_ENVIRONMENT diff --git a/src/scriptInterface.cpp b/src/scriptInterface.cpp deleted file mode 100644 index 1f37d0b0..00000000 --- a/src/scriptInterface.cpp +++ /dev/null @@ -1,790 +0,0 @@ -#include -#include - -#include "random.h" -#include "resources.h" -#include "scriptInterface.h" -#include "scriptInterfaceSandbox.h" - -static int random(lua_State* L) -{ - float rMin = static_cast(luaL_checknumber(L, 1)); - float rMax = static_cast(luaL_checknumber(L, 2)); - rMin = std::min(rMin, rMax); - lua_pushnumber(L, random(rMin, rMax)); - return 1; -} -/// float random(float min_value, float max_value) -/// Returns a random floating point number between the min and max values, inclusive. -/// Floating point numbers are fractional numbers, such as 1.5, 2.333333, 3.141. -/// To generate an integer value, use irandom(). -/// This function is provided by SeriousProton (src/scriptInterface.cpp). -/// Example: value = random(0.0,1.0) -REGISTER_SCRIPT_FUNCTION(random); - -static int irandom(lua_State* L) -{ - int rMin = static_cast(luaL_checkinteger(L, 1)); - int rMax = static_cast(luaL_checkinteger(L, 2)); - rMin = std::min(rMin, rMax); - lua_pushinteger(L, irandom(rMin, rMax)); - return 1; -} -/// int irandom(int min_value, int max_value) -/// Returns a random integer number between the min and max values, inclusive. -/// Integer numbers are whole numbers, so 1, 2, 3, 5, 1400. -/// To generate a floating point value, use random(). -/// This function is provided by SeriousProton (src/scriptInterface.cpp). -/// Example: value = random(0,10) -REGISTER_SCRIPT_FUNCTION(irandom); - -static int traceback(lua_State* L) -{ - string result; - int level = 1; - lua_Debug debug; - while (lua_getstack(L, level++, &debug)) { - lua_getinfo(L, "Slnt", &debug); - result += string(debug.source); - if (debug.currentline > 0) - result += ":" + string(debug.currentline); - if (debug.name && debug.name[0]) - result += ":" + string(debug.name); - result += "\n"; - } - lua_pushstring(L, result.c_str()); - return 1; -} -/// string traceback() -/// Returns a string containing a list of function calls up to the current point. -/// Use this function for debugging and error reporting. -/// This function is provided by SeriousProton (src/scriptInterface.cpp). -/// Example: -/// player:getHull() -/// traceback = traceback() -/// -- traceback contains the string [[player:getHull() -/// -- traceback = traceback()]] -REGISTER_SCRIPT_FUNCTION(traceback); - -static int destroyScript(lua_State* L) -{ - ScriptObject* obj = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - obj->destroy(); - return 0; -} -/// void destroyScript() -/// Destroys this script instance. -/// The script continues running until the end of the current script call. -/// This function is provided by SeriousProton (src/scriptInterface.cpp). -//REGISTER_SCRIPT_FUNCTION(destroyScript);//Not registered as a normal function, as it needs a reference to the ScriptObject, which is passed as an upvalue. - -lua_State* ScriptObject::L = NULL; - -ScriptObject::ScriptObject() -{ - max_cycle_count = 0; - - createLuaState(); -} - -ScriptObject::ScriptObject(string filename) -{ - max_cycle_count = 0; - - createLuaState(); - run(filename); -} - -static const luaL_Reg loadedlibs[] = { - {"_G", luaopen_base}, -// {LUA_LOADLIBNAME, luaopen_package}, -// {LUA_COLIBNAME, luaopen_coroutine}, - {LUA_TABLIBNAME, luaopen_table}, -// {LUA_IOLIBNAME, luaopen_io}, -// {LUA_OSLIBNAME, luaopen_os}, - {LUA_STRLIBNAME, luaopen_string}, -// {LUA_BITLIBNAME, luaopen_bit32}, - {LUA_MATHLIBNAME, luaopen_math}, -// {LUA_DBLIBNAME, luaopen_debug}, - {NULL, NULL} -}; - -static const char* safe_functions[] = { - "assert", "error", "getmetatable", "ipairs", "next", "pairs", "pcall", - "print", "rawequal", "rawget", "rawlen", "rawset", "require", "select", - "setmetatable", "tonumber", "tostring", "type", "xpcall", - NULL, -}; - -void ScriptObject::createLuaState() -{ - if (L == NULL) - { - L = luaL_newstate(); - - /* call open functions from 'loadedlibs' and set results to global table */ - for (const luaL_Reg *lib = loadedlibs; lib->func; lib++) - { - luaL_requiref(L, lib->name, lib->func, 1); - lua_pop(L, 1); /* remove lib */ - } - - // Protect the metatable the string library sets on strings. - // This metatable points to the global `string` library, rather than the environment's copy. - // This global string library table can't be accessed directly, but it means a function defining - // `function string.foo(...)` can't call that function as `"some_string":foo()` since the metatable - // points to the global version rather than its own copy. - // TODO see if this can be fixed without breaking the sandbox. probably not. - lua_pushstring(L, ""); - protectLuaMetatable(L); - lua_pop(L, 1); - } - - //Setup a new table as the first upvalue. This will be used as "global" environment for the script. And thus will prevent global namespace polution. - lua_newtable(L); /* environment for loaded function */ - - // set a _G in the script's environment pointing to its own global environment - lua_pushstring(L, "_G"); - lua_pushvalue(L, -2); - lua_rawset(L, -3); - - // Copy in the safe global functions. - for (const char **fn = safe_functions; *fn; fn++) - { - lua_pushstring(L, *fn); - lua_getglobal(L, *fn); - lua_rawset(L, -3); - } - - // Copy in the global libraries. - for (const luaL_Reg *lib = loadedlibs; lib->func; lib++) - { - if (!strcmp(lib->name, "_G")) { - continue; - } - - // Make a table for the library. - lua_newtable(L); // [env] [local] - - // Set it into the script environment. - lua_pushstring(L, lib->name); // [env] [local] [libname] - lua_pushvalue(L, -2); // [env] [local] [libname] [local] - lua_rawset(L, -4); // [env] [local] - - // Iterate the global library. - lua_getglobal(L, lib->name); // [env] [local] [global] - lua_pushnil(L); // [env] [local] [global] nil - while (lua_next(L, -2)) - { // [env] [local] [global] [key] [value] - if (!lua_isfunction(L, -1) && !lua_isnumber(L, -1)) - { - // This doesn't trigger on anything right now; it's here in case anything gets added to any of the libraries that would potentially break the sandbox. - LOG(WARNING) << "ignoring non-{function,number} in " << lib->name; - lua_pop(L, 1); - continue; - } - - // Functions and numbers are safe to share - copy the value into the script's library table - lua_pushvalue(L, -2); // [env] [local] [global] [key] [value] [key] - lua_rotate(L, -2, 1); // [env] [local] [global] [key] [key] [value] - lua_rawset(L, -5); // [env] [local] [global] [key] - } - // [env] [local] [global] - lua_pop(L, 2); // [env] - } - - //Register all global functions for our game. - for(ScriptClassInfo* item = scriptClassInfoList; item != NULL; item = item->next) - { - item->register_function(L); - - lua_pushstring(L, item->class_name.c_str()); - lua_getglobal(L, item->class_name.c_str()); - lua_rawset(L, -3); - } - - //Register the destroyScript function. This needs a reference back to the script object, we pass this as an upvalue. - lua_pushstring(L, "destroyScript"); - lua_pushlightuserdata(L, this); - lua_pushcclosure(L, destroyScript, 1); - lua_rawset(L, -3); - - //Register a pointer to this script object in the environment. So we can get a reference back to this object. - lua_pushstring(L, "__script_pointer"); - lua_pushlightuserdata(L, this); - lua_rawset(L, -3); - - //Register the environment table for this script object in the registry. - lua_pushlightuserdata(L, this); - lua_pushvalue(L, -2); - lua_settable(L, LUA_REGISTRYINDEX); - - //Pop the environment table from the stack - lua_pop(L, 1); -} - -bool ScriptObject::run(string filename) -{ - setCycleLimit(); - - LOG(INFO) << "Load script: " << filename; - P stream = getResourceStream(filename); - if (!stream) - { - LOG(ERROR) << "Script not found: " << filename; - return false; - } - - string filecontents; - do - { - string line = stream->readLine(); - filecontents += line + "\n"; - }while(stream->tell() < stream->getSize()); - - if (luaL_loadbuffer(L, filecontents.c_str(), filecontents.length(), filename.c_str())) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: load: " << error_string; - lua_pop(L, 1); - return false; - } - - //Get the environment table from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - //set the environment table it as 1st upvalue - lua_setupvalue(L, -2, 1); - - //Call the actual code. - if (lua_pcall(L, 0, 0, 0)) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: run: " << error_string; - lua_pop(L, 1); - return false; - } - - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - lua_pushstring(L, "init"); - lua_rawget(L, -2); - lua_remove(L, -2); - - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - //LOG(WARNING) << "WARNING(no init function): " << filename; - }else if (lua_pcall(L, 0, 0, 0)) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: init: " << error_string; - lua_pop(L, 1); - return false; - } - return true; -} - -void ScriptObject::setVariable(string variable_name, string value) -{ - //Get the environment table from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - - //Set our variable in this environment table - lua_pushstring(L, variable_name.c_str()); - lua_pushstring(L, value.c_str()); - lua_rawset(L, -3); - - //Pop the table - lua_pop(L, 1); -} - -void ScriptObject::registerObject(P object, string variable_name) -{ - //Get the environment table from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - - //Set our global in this environment table - lua_pushstring(L, variable_name.c_str()); - - if (convert< P >::returnType(L, object)) - { - lua_rawset(L, -3); - //Pop the environment table - lua_pop(L, 1); - }else{ - LOG(ERROR) << "Failed to find class for object " << variable_name; - //Need to pop the variable name and the environment table. - lua_pop(L, 2); - } -} - -bool ScriptObject::runCode(string code) -{ - setCycleLimit(); - - if (luaL_loadstring(L, code.c_str())) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: " << code << ": " << error_string; - lua_pop(L, 1); - return false; - } - - //Get the environment table from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - //Set it as the first upvalue so it becomes the environment for this call. - lua_setupvalue(L, -2, 1); - - if (lua_pcall(L, 0, LUA_MULTRET, 0)) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: " << code << ": " << error_string; - lua_pop(L, 1); - return false; - } - lua_settop(L, 0); - return true; -} - -static string luaToJSON(lua_State* L, int index) -{ - if (lua_isnil(L, index) || lua_isnone(L, index)) - return "null"; - if (lua_isboolean(L, index)) - return lua_toboolean(L, index) ? "true" : "false"; - if (lua_isnumber(L, index)) - return string(static_cast(lua_tonumber(L, index)), 3); - if (lua_isstring(L, index)) - return "\"" + string(lua_tostring(L, index)) + "\""; - if (lua_istable(L, index)) - { - string ret = "{"; - lua_pushnil(L); - bool first = true; - while(lua_next(L, index) != 0) - { - if (first) - first = false; - else - ret += ", "; - /* uses 'key' (at index -2) and 'value' (at index -1) */ - ret += "\"" + string(luaL_tolstring(L, lua_gettop(L) - 1, nullptr)) + "\""; - lua_pop(L, 1); - ret += ": "; - ret += luaToJSON(L, lua_gettop(L)); - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(L, 1); - } - ret += "}"; - return ret; - } - if (lua_isuserdata(L, index)) - return "\"[OBJECT]\""; - if (lua_isfunction(L, index)) - return "\"[function]\""; - return "???"; -} - -bool ScriptObject::runCode(string code, string& json_output) -{ - if (!L) - return false; - if (luaL_loadstring(L, code.c_str())) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: " << code << ": " << error_string; - lua_pop(L, 1); - return false; - } - - //Get the environment table from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - //Set it as the first upvalue so it becomes the environment for this call. - lua_setupvalue(L, -2, 1); - - if (lua_pcall(L, 0, LUA_MULTRET, 0)) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: " << code << ": " << error_string; - lua_pop(L, 1); - return false; - } - int nresults = lua_gettop(L); - json_output = ""; - for(int n=0; n 0) - json_output += ", "; - json_output += luaToJSON(L, n + 1); - } - lua_settop(L, 0); - return true; -} - -bool ScriptObject::callFunction(string name) -{ - setCycleLimit(); - - //Get our environment from the registry - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - //Get the function from the environment - lua_pushstring(L, name.c_str()); - lua_rawget(L, -2); - //Call the function - if (lua_pcall(L, 0, 0, 0)) - { - error_string = luaL_checkstring(L, -1); - LOG(ERROR) << "LUA: " << name << ": " << error_string; - lua_pop(L, 2); - return false; - } - lua_pop(L, 1); - return true; -} - -static void runCyclesHook(lua_State *L, lua_Debug */*ar*/) -{ - lua_pushstring(L, "Max execution limit reached. Aborting."); - lua_error(L); -} - -void ScriptObject::setCycleLimit() -{ - if (max_cycle_count) - lua_sethook(L, runCyclesHook, LUA_MASKCOUNT, max_cycle_count); - else - lua_sethook(L, NULL, 0, 0); -} - -void ScriptObject::setMaxRunCycles(int count) -{ - max_cycle_count = count; -} - -ScriptObject::~ScriptObject() -{ - //Remove our environment from the registry. - lua_pushlightuserdata(L, this); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); -} - -string ScriptObject::getError() -{ - return error_string; -} - -void ScriptObject::update(float delta) -{ - setCycleLimit(); - - // Get the reference to our environment from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - // Get the update function from the script environment - lua_pushstring(L, "update"); - lua_rawget(L, -2); - - // If it's a function, call it, if not, pop the environment and the function from the stack. - if (!lua_isfunction(L, -1)) - { - lua_pop(L, 2); - }else{ - lua_pushnumber(L, delta); - if (lua_pcall(L, 1, 0, 0)) - { - LOG(ERROR) << "LUA: update: " << luaL_checkstring(L, -1); - lua_pop(L, 2); - return; - } - lua_pop(L, 1); - } -} - -void ScriptObject::destroy() -{ - //Remove our environment from the registry. - lua_pushlightuserdata(L, this); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); - - Updatable::destroy(); - - //Running the garbage collector here is good for memory cleaning. - lua_gc(L, LUA_GCCOLLECT, 0); -} - -void ScriptObject::clearDestroyedObjects() -{ - if (!L) - return; -#ifdef DEBUG - //Run the garbage collector every update when debugging, to better debug references and leaks. - lua_gc(L, LUA_GCCOLLECT, 0); -#endif - if (lua_gettop(L) != 0) - LOG(WARNING) << "lua_gettop != 0, could indicate an error in the lua bindings! (" << lua_gettop(L) << ")"; - - lua_pushnil(L); - while (lua_next(L, LUA_REGISTRYINDEX) != 0) - { - if (lua_islightuserdata(L, -2) && lua_istable(L, -1)) - { - lua_pushstring(L, "__ptr"); - lua_rawget(L, -2); - if (lua_isuserdata(L, -1)) - { - P** p = static_cast< P** >(lua_touserdata(L, -1)); - if (***p == NULL) - { - lua_pushvalue(L, -3); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); - } - } - lua_pop(L, 1); - } - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(L, 1); - } -} - -ScriptCallback::ScriptCallback() -{ -} - -ScriptCallback::~ScriptCallback() -{ - lua_State* L = ScriptObject::L; - - //Remove ourselves from the registry. - lua_pushlightuserdata(L, this); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); -} - -void ScriptCallback::operator() () -{ - lua_State* L = ScriptObject::L; - - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - if (!lua_istable(L, -1)) - { - lua_pop(L, 1); - return; - } - - lua_pushnil(L); - while (lua_next(L, -2) != 0) - { - if (lua_istable(L, -1)) - { - lua_pushstring(L, "script_pointer"); - lua_rawget(L, -2); - //Check if the script pointer is still available as key in the registry. If not, this reference is no longer valid and needs to be removed. - lua_gettable(L, LUA_REGISTRYINDEX); - if (!lua_istable(L, -1)) - { - //Stack is [callback_table] [callback_key] [callback_entry_table] [script_pointer] - lua_pushvalue(L, -3); - lua_pushnil(L); - lua_rawset(L, -6); - lua_pop(L, 1); - }else{ - lua_pop(L, 1); - - lua_pushstring(L, "function"); - lua_rawget(L, -2); - - lua_sethook(L, NULL, 0, 0); - if (lua_pcall(L, 0, 0, 0)) - { - LOG(ERROR) << "Callback function error: " << lua_tostring(L, -1); - lua_pop(L, 1); - } - } - } - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(L, 1); - } - lua_pop(L, 1); -} - -ScriptSimpleCallback::ScriptSimpleCallback() -{ - //The ScriptSimpleCallback simply stores a single table with a script object reference and a function reference, - // this is stored in the registry at the pointer location of this object. -} - -ScriptSimpleCallback::~ScriptSimpleCallback() -{ - lua_State* L = ScriptObject::L; - - //Remove ourselves from the registry. - lua_pushlightuserdata(L, this); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); -} - -ScriptSimpleCallback::ScriptSimpleCallback(const ScriptSimpleCallback& other) -{ - *this = other; -} - -ScriptSimpleCallback& ScriptSimpleCallback::operator =(const ScriptSimpleCallback& other) -{ - lua_State* L = ScriptObject::L; - - //First push our own pointer on the stack, we will need this later. - lua_pushlightuserdata(L, this); - - //Get the table of the other ScriptSimpleCallback from the registry - lua_pushlightuserdata(L, (void*)&other); - lua_gettable(L, LUA_REGISTRYINDEX); - - //The stack is now [this], [table from other]. So call settable to store this table as our own reference as well. - lua_settable(L, LUA_REGISTRYINDEX); - - return *this; -} - -bool ScriptSimpleCallback::isSet() -{ - lua_State* L = ScriptObject::L; - - //First get our simple table from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - if (!lua_istable(L, -1)) - { - lua_pop(L, 1); - return false; - } - - //Push the key "script_pointer" to retrieve the pointer to this script object. - lua_pushstring(L, "script_pointer"); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) - { - lua_pop(L, 2); - return true; - } - //Stack is: [table] [pointer to script object] - lua_pushvalue(L, -1); - //Stack is: [table] [pointer to script object] [pointer to script object] - //Check if the script pointer is still available as key in the registry. If not, this reference is no longer valid and needs to be removed. - lua_gettable(L, LUA_REGISTRYINDEX); - //Stack is: [table] [pointer to script object] [table script object or nil when object is destroyed] - if (!lua_istable(L, -1)) - { - lua_pop(L, 3); - return false; - } - lua_pop(L, 3); - return true; -} - -void ScriptSimpleCallback::clear() -{ - lua_State* L = ScriptObject::L; - - //Remove ourselves from the registry. - lua_pushlightuserdata(L, this); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); -} - -P ScriptSimpleCallback::getScriptObject() -{ - lua_State* L = ScriptObject::L; - - //First get our simple table from the registry. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - if (!lua_istable(L, -1)) - { - lua_pop(L, 1); - return nullptr; - } - - //Push the key "script_pointer" to retrieve the pointer to this script object. - lua_pushstring(L, "script_pointer"); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) - { - lua_pop(L, 2); - return nullptr; - } - //Stack is: [table] [pointer to script object] - lua_pushvalue(L, -1); - //Stack is: [table] [pointer to script object] [pointer to script object] - //Check if the script pointer is still available as key in the registry. If not, this reference is no longer valid and needs to be removed. - lua_gettable(L, LUA_REGISTRYINDEX); - //Stack is: [table] [pointer to script object] [table script object or nil when object is destroyed] - if (!lua_istable(L, -1)) - { - lua_pop(L, 3); - return nullptr; - } - P ret = static_cast(lua_touserdata(L, -2)); - lua_pop(L, 3); - return ret; -} - -template<> void convert::param(lua_State* L, int& idx, ScriptSimpleCallback& callback_object) -{ - if (lua_isnil(L, idx)) - { - //Nil given as parameter to this callback, clear out the callback. - lua_pushlightuserdata(L, &callback_object); - lua_pushnil(L); - lua_settable(L, LUA_REGISTRYINDEX); - idx++; - return; - } - //Check if the parameter is a function. - luaL_checktype(L, idx, LUA_TFUNCTION); - //Check if this function is a lua function, with an reference to the environment. - // (We need the environment reference to see if the script to which the function belongs is destroyed when calling the callback) - if (lua_iscfunction(L, idx)) - luaL_error(L, "Cannot set a binding as callback function."); - lua_getupvalue(L, idx, 1); - if (!lua_istable(L, -1)) - luaL_error(L, "??[convert::param] Upvalue 1 of function is not a table..."); - //Stack is now: [function_environment] - - lua_pushlightuserdata(L, &callback_object); - lua_newtable(L); - lua_pushstring(L, "script_pointer"); - - //Stack is now: [function_environment] [callback object pointer] [table] "script_pointer" - lua_pushstring(L, "__script_pointer"); - lua_rawget(L, -5); - if (lua_isnil(L, -1)) - { - //Simple functions that do not access globals do not inherit their environment from their creator, so they have nil here. - }else if (!lua_islightuserdata(L, -1)) - { - luaL_error(L, "??[convert::param] Cannot find reference back to script..."); - } - //Stack is now: [function_environment] [callback object pointer] [table] "script_pointer" [pointer to script object] - lua_rawset(L, -3); - //Stack is now: [function_environment] [callback object pointer] [table] - lua_pushstring(L, "function"); - lua_pushvalue(L, idx); - //Stack is now: [function_environment] [callback object pointer] [table] "function" [lua function reference] - lua_rawset(L, -3); - - //Stack is now: [function_environment] [callback object pointer] [table] - lua_settable(L, LUA_REGISTRYINDEX); - lua_pop(L, 1); - - idx++; -} diff --git a/src/scriptInterface.h b/src/scriptInterface.h deleted file mode 100644 index 7026b07b..00000000 --- a/src/scriptInterface.h +++ /dev/null @@ -1,241 +0,0 @@ -#ifndef SCRIPT_INTERFACE_H -#define SCRIPT_INTERFACE_H - -#include "scriptInterfaceMagic.h" -#include "stringImproved.h" -#include "Updatable.h" -#include "vectorUtils.h" - - -class ScriptObject : public Updatable -{ - static lua_State* L; - - int max_cycle_count; - string error_string; -public: - ScriptObject(); - ScriptObject(string filename); - virtual ~ScriptObject(); - - bool run(string filename); - void registerObject(P object, string variable_name); - void setVariable(string variable_name, string value); - bool runCode(string code); - bool runCode(string code, string& json_output); - string getError(); - bool callFunction(string name); - void setMaxRunCycles(int count); - virtual void update(float delta) override; - - virtual void destroy() override; - - static void clearDestroyedObjects(); -private: - void createLuaState(); - void setCycleLimit(); - - //Make the ScriptCallback our friend, so we can access the lua_State from the callback class. - friend class ScriptCallback; - friend class ScriptSimpleCallback; -}; - -class ScriptCallback : sp::NonCopyable -{ -public: - ScriptCallback(); - ~ScriptCallback(); - - void operator() (); -}; - -/** - Simple callback to the scripting interface. - This callback only holds a single reference to a script function, is copyable, and can be used as parameter for script binded functions. -*/ -class ScriptSimpleCallback -{ -private: - - template - int pushArgs(lua_State* L, ARG&& arg, ARGS&&... args) - { - int headItemsPushedToStack = pushArgs(L, std::forward(arg)); - int tailItemsPushedToStack = pushArgs(L, std::forward(args)...); - return headItemsPushedToStack + tailItemsPushedToStack; - } - template - int pushArgs(lua_State* L, T&& thing) - { - if (!convert::returnType(L, std::forward(thing))) - { - // nothing was pushed on the stack. Push nil explicitly to maintain argument positions - lua_pushnil(L); - } - return 1; - } - int pushArgs(lua_State* L) - { - return 0; - } - -public: - ScriptSimpleCallback(); - ~ScriptSimpleCallback(); - - ScriptSimpleCallback(const ScriptSimpleCallback&); - ScriptSimpleCallback& operator =(const ScriptSimpleCallback&); - - //Returns true if this callback is set and the scriptobject that set it is still valid. - bool isSet(); - - // Call this script function. - // Returns a value-less optional when the executed function is no longer available, on nil, or an error occurred. - // else it will return a set optional with the return value from lua. - template - std::conditional_t, void, std::optional> call(Args&&... args) - { - static_assert(std::is_void_v - || std::is_arithmetic_v - || std::is_enum_v - || std::is_convertible_v, "return type must be: void, bool, a number, an enum or a string (std::string or SP's)."); - - lua_State* L = ScriptObject::L; - - //Get the simple table from the registry. If it's not available, then this callback was never set to anything. - lua_pushlightuserdata(L, this); - lua_gettable(L, LUA_REGISTRYINDEX); - if (!lua_istable(L, -1)) - { - lua_pop(L, 1); - if constexpr (std::is_void_v) - return; - else - return {}; - } - //Stack is: [table] - - //Push the key "script_pointer" to retrieve the pointer to this script object. - lua_pushstring(L, "script_pointer"); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) - { - //Callback function didn't have an script environment attached to it, so we cannot check if that script still exists. - } - else - { - //Stack is: [table] [pointer to script object] - //Check if the script pointer is still available as key in the registry. If not, this reference is no longer valid and needs to be removed. - lua_gettable(L, LUA_REGISTRYINDEX); - if (!lua_istable(L, -1)) - { - lua_pop(L, 2); - - if constexpr (std::is_void_v) - return; - else - return {}; - } - } - //Remove the script pointer table from the stack, we only needed to check if it exists. - lua_pop(L, 1); - //Stack is: [table] - - //Next get our actual function from the stack - lua_pushstring(L, "function"); - lua_rawget(L, -2); - - int i = pushArgs(L, std::forward(args)...); - if (i < 0) // error condition - { - lua_pop(L, 2); - if constexpr (std::is_void_v) - return; - else - return {}; - } - - //Stack is: [table] [lua function] - lua_sethook(L, NULL, 0, 0); - if (lua_pcall(L, i, 1, 0)) - { - LOG(ERROR) << "Callback function error: " << lua_tostring(L, -1); - last_error = lua_tostring(L, -1); - lua_pop(L, 2); - if constexpr (std::is_void_v) - return; - else - return {}; - } - - //Stack is: [table] [call result] - if constexpr (std::is_void_v) - { - if (!lua_isnoneornil(L, -1)) - { - LOG(DEBUG) << "return value discarded."; - } - lua_pop(L, 2); - return; - } - else - { - std::optional result; - if (!lua_isnoneornil(L, -1)) - { - if constexpr (std::is_convertible_v) - { - if (lua_isstring(L, -1)) - { - result.emplace(lua_tostring(L, -1)); - } - else - { - LOG(ERROR) << "Unexpected return type (string wanted)"; - } - } - else if constexpr (std::is_same_v) - { - // No check - it's falsy / truthy values. - result.emplace(static_cast(lua_toboolean(L, -1))); - } - else if constexpr (std::is_integral_v || std::is_enum_v) - { - if (lua_isinteger(L, -1)) - { - result.emplace(static_cast(lua_tointeger(L, -1))); - } - else - { - LOG(ERROR) << "Unexpected return type (integer wanted)"; - } - } - else if constexpr (std::is_floating_point_v) - { - if (lua_isnumber(L, -1)) - { - result.emplace(static_cast(lua_tonumber(L, -1))); - } - else - { - LOG(ERROR) << "Unexpected return type (floating point wanted)"; - } - } - } - - lua_pop(L, 2); - return result; - } - } - - //Unset this script callback reference. - void clear(); - - //Return the script object linked to this callback, if any. - P getScriptObject(); - - static inline string last_error; -}; -template<> void convert::param(lua_State* L, int& idx, ScriptSimpleCallback& callback); - -#endif//SCRIPT_INTERFACE_H diff --git a/src/scriptInterfaceMagic.cpp b/src/scriptInterfaceMagic.cpp deleted file mode 100644 index ba33d5a1..00000000 --- a/src/scriptInterfaceMagic.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "scriptInterfaceMagic.h" - -ScriptClassInfo* scriptClassInfoList; - -static string getObjectClassNameRecursive(P object, ScriptClassInfo* info) -{ - if (info->check_function && info->check_function(object)) - { - for(unsigned int n=0; nchild_classes.size(); n++) - { - string sub_name = getObjectClassNameRecursive(object, info->child_classes[n]); - if (sub_name != "") - return sub_name; - } - return info->class_name; - } - return ""; -} - -string getScriptClassClassNameFromObject(const P& object) -{ - static bool child_relations_set = false; - if (!child_relations_set) - { - for(ScriptClassInfo* item = scriptClassInfoList; item != NULL; item = item->next) - { - if (item->base_class_name != "") - { - for(ScriptClassInfo* item_parent = scriptClassInfoList; item_parent != NULL; item_parent = item_parent->next) - { - if (item->base_class_name == item_parent->class_name) - { - item_parent->child_classes.push_back(item); - } - } - } - } - child_relations_set = true; - } - - for(ScriptClassInfo* item = scriptClassInfoList; item != NULL; item = item->next) - { - if (item->base_class_name == "") - { - string ret = getObjectClassNameRecursive(object, item); - if (ret != "") - return ret; - } - } - return ""; -} - -template<> int convert::returnType(lua_State* L, bool b) -{ - lua_pushboolean(L, b); - return 1; -} - -template<> int convert::returnType(lua_State* L, const string& s) -{ - lua_pushlstring(L, s.c_str(), s.length()); - return 1; -} - -template<> void convert::param(lua_State* L, int& idx, const char*& str) -{ - str = luaL_checkstring(L, idx++); -} - -template<> void convert::param(lua_State* L, int& idx, string& str) -{ - str = luaL_checkstring(L, idx++); -} - - -template<> void convert::param(lua_State* L, int& idx, std::string_view& str) -{ - str = luaL_checkstring(L, idx++); -} - -template<> void convert::param(lua_State* L, int& idx, bool& b) -{ - b = lua_toboolean(L, idx++); -} - -template<> void convert::param(lua_State* L, int& idx, glm::u8vec4& color) -{ - color = glm::u8vec4(255,255,255,255); - string str = string(luaL_checkstring(L, idx++)).lower(); - if (str == "black") { color = glm::u8vec4(0,0,0,255); return; } - else if (str == "white") { color = glm::u8vec4(255,255,255,255); return; } - else if (str == "red") { color = glm::u8vec4(255,0,0,255); return; } - else if (str == "green") { color = glm::u8vec4(0,255,0,255); return; } - else if (str == "blue") { color = glm::u8vec4(0,0,255,255); return; } - else if (str == "yellow") { color = glm::u8vec4(255,255,0,255); return; } - else if (str == "magenta") { color = glm::u8vec4(255,0,255,255); return; } - else if (str == "cyan") { color = glm::u8vec4(0,255,255,255); return; } - - if (str.startswith("#") && str.length() == 7) - { - color.r = static_cast(str.substr(1, 2).toInt(16)); - color.g = static_cast(str.substr(3, 2).toInt(16)); - color.b = static_cast(str.substr(5, 2).toInt(16)); - } - - std::vector parts = str.split(","); - if (parts.size() == 3) - { - color.r = static_cast(parts[0].toInt()); - color.g = static_cast(parts[1].toInt()); - color.b = static_cast(parts[2].toInt()); - } -} diff --git a/src/scriptInterfaceMagic.h b/src/scriptInterfaceMagic.h deleted file mode 100644 index 7029741b..00000000 --- a/src/scriptInterfaceMagic.h +++ /dev/null @@ -1,732 +0,0 @@ -#ifndef SCRIPT_INTERFACE_MAGIC_H -#define SCRIPT_INTERFACE_MAGIC_H - -/** - Warning, here be dragons. - This code links the LUA script engine to C++ classes with a lot of template magic. - It does automatic parameter conversion, as well as handling "dead" objects. -*/ - -#include "P.h" -#include "stringImproved.h" -#include "lua/lua.hpp" -#include "scriptInterfaceSandbox.h" -#include "glm/gtc/type_precision.hpp" -#include -#include - -class ScriptClassInfo; - -typedef void (*registerObjectFunction)(lua_State* L); -typedef bool (*checkObjectType)(P obj); - -extern ScriptClassInfo* scriptClassInfoList; -class ScriptClassInfo -{ -public: - string class_name; - string base_class_name; - registerObjectFunction register_function; - checkObjectType check_function; - ScriptClassInfo* next; - std::vector child_classes; - - ScriptClassInfo(string class_name, string base_class_name, registerObjectFunction register_function, checkObjectType check_function) - { - this->class_name = class_name; - this->base_class_name = base_class_name; - this->register_function = register_function; - this->check_function = check_function; - this->next = scriptClassInfoList; - scriptClassInfoList = this; - } -}; - -string getScriptClassClassNameFromObject(const P& object); - -template -struct convert -{ - // Pick the return *type* based on input: - // If it's a number-ish format, use it as is. - // Otherwise use a const reference. - using return_t = std::conditional_t< - std::is_arithmetic_v || std::is_enum_v, T, - std::add_lvalue_reference_t< - std::add_const_t - > - >; - /* all parameters are by reference. */ - static void param(lua_State* L, int& idx, std::add_lvalue_reference_t t); - static int returnType(lua_State* L, return_t t); -}; - -template -void convert::param(lua_State* L, int& idx, std::add_lvalue_reference_t t) -{ - //If you get a compile error here, then the function you are trying to register has an parameter that is not handled by the specialized converters, nor - // by the default number conversion. - if constexpr (std::is_integral_v) - t = static_cast(luaL_checkinteger(L, idx++)); - else - t = static_cast(luaL_checknumber(L, idx++)); -} - -template -int convert::returnType(lua_State* L, return_t t) -{ - if constexpr (!std::is_reference_v) - { - if constexpr (std::is_integral_v) - lua_pushinteger(L, t); - else - lua_pushnumber(L, t); - return 1; - } - else - { - // We got a reference - just delegate to the non-reference implementation. - return convert>::returnType(L, t); - } - -} -//Specialized template for the bool return type, so we return a lua boolean. -template<> int convert::returnType(lua_State* L, bool b); -//Specialized template for the string return type, so we return a lua string. -template<> int convert::returnType(lua_State* L, const string& s); - -//Have optional parameters, provided they are last arguments of script function -template -struct convert> //the >> power of c++17 -{ - static void param(lua_State* L, int& idx, std::optional& opt_t) - { - if (lua_gettop(L) >= idx) //If there are still arguments unwinded - { - T res; - convert::param(L,idx,res); - opt_t = res; - } - } -}; - -/* Convert parameters to PObject pointers */ -template -struct convert -{ - static void param(lua_State* L, int& idx, T*& ptr) - { - static_assert(std::is_base_of::value, "T must be a descendant of PObject"); - - if (!lua_istable(L, idx)) - { - const char *msg = lua_pushfstring(L, "Object expected, got %s", luaL_typename(L, idx)); - luaL_argerror(L, idx, msg); - return; - } - lua_pushstring(L, "__ptr"); - lua_rawget(L, idx++); - - P** p = static_cast< P** >(lua_touserdata(L, -1)); - lua_pop(L, 1); - if (p == NULL) - { - ptr = NULL; - const char *msg = lua_pushfstring(L, "Object expected, got %s", luaL_typename(L, idx-1)); - luaL_argerror(L, idx-1, msg); - return; - } - if (*p == NULL) - { - ptr = NULL; - return; - } - ptr = dynamic_cast(***p); - //printf("ObjParam: %p\n", ptr); - } -}; - -template -struct convert> -{ - static_assert(std::is_base_of_v, "T must be a derived class of PObject."); - static void param(lua_State* L, int& idx, P& ptr) - { - if (!lua_istable(L, idx)) - { - const char *msg = lua_pushfstring(L, "Object expected, got %s", luaL_typename(L, idx)); - luaL_argerror(L, idx, msg); - return; - } - lua_pushstring(L, "__ptr"); - lua_rawget(L, idx++); - - P** p = static_cast< P** >(lua_touserdata(L, -1)); - lua_pop(L, 1); - if (p == NULL) - { - ptr = NULL; - const char *msg = lua_pushfstring(L, "Object expected, got %s", luaL_typename(L, idx-1)); - luaL_argerror(L, idx-1, msg); - return; - } - if (*p == NULL) - { - ptr = NULL; - const char* msg = lua_pushliteral(L, "Object expected, got destroyed object"); - luaL_argerror(L, idx-1, msg); - return; - } - ptr = **p; - //printf("ObjParam: %p\n", ptr); - } - - static int returnType(lua_State* L, const P& object) - { - PObject* ptr = *object; - if (!ptr) - return 0; - - //Try to find this object in the global registry. - lua_pushlightuserdata(L, ptr); - lua_gettable(L, LUA_REGISTRYINDEX); - if (lua_istable(L, -1)) - { - //Object table already created, so just return a reference to that. - return 1; - } - lua_pop(L, 1); - - string class_name = getScriptClassClassNameFromObject(ptr); - if (class_name != "") - { - lua_newtable(L); - - luaL_getmetatable(L, class_name.c_str()); - lua_setmetatable(L, -2); - - lua_pushstring(L, "__ptr"); - P** p = static_cast< P** >(lua_newuserdata(L, sizeof(P*))); - *p = new P(); - (**p) = ptr; - - lua_rawset(L, -3); - - lua_pushlightuserdata(L, ptr); - lua_pushvalue(L, -2); - lua_settable(L, LUA_REGISTRYINDEX); - - return 1; - } - return 0; - } -}; -template -struct convert> -{ - static_assert(std::is_base_of_v, "T must be a derived class of PObject."); - static int returnType(lua_State* L, const PVector& pvector) - { - return convert>>::returnType(L, pvector); - } -}; - -//Specialized template for const char* so we can convert lua strings to C strings. This overrules the general T* template for const char* -template<> void convert::param(lua_State* L, int& idx, const char*& str); -template<> void convert::param(lua_State* L, int& idx, string& str); -template<> void convert::param(lua_State* L, int& idx, std::string_view& str); - -template<> void convert::param(lua_State* L, int& idx, bool& b); - -template -struct convert> -{ - static void param(lua_State* L, int& idx, glm::vec<2, T, Q>& v) - { - convert::param(L, idx, v.x); - convert::param(L, idx, v.y); - } - - static int returnType(lua_State* L, const glm::vec<2, T, Q>& t) - { - auto result = convert::returnType(L, t.x); - result += convert::returnType(L, t.y); - return result; - } -}; -template -struct convert> -{ - static void param(lua_State* L, int& idx, glm::vec<3, T, Q>& v) - { - convert::param(L, idx, v.x); - convert::param(L, idx, v.y); - convert::param(L, idx, v.z); - } - - static int returnType(lua_State* L, const glm::vec<3, T, Q>& t) - { - auto result = convert::returnType(L, t.x); - result += convert::returnType(L, t.y); - result += convert::returnType(L, t.z); - return result; - } -}; - -template -struct convert> -{ - static void param(lua_State* L, int& idx, glm::vec<4, T, Q>& v) - { - convert::param(L, idx, v.x); - convert::param(L, idx, v.y); - convert::param(L, idx, v.z); - convert::param(L, idx, v.w); - } - - static int returnType(lua_State* L, const glm::vec<4, T, Q>& t) - { - auto result = convert::returnType(L, t.x); - result += convert::returnType(L, t.y); - result += convert::returnType(L, t.z); - result += convert::returnType(L, t.w); - return result; - } -}; - -template<> void convert::param(lua_State* L, int& idx, glm::u8vec4& color); - -/* Convert parameters to std::vector objects. */ -template -struct convert> -{ - static void param(lua_State* L, int& idx, std::vector& v) - { - while (idx <= lua_gettop(L)) - { - convert::param(L, idx, v.emplace_back()); - } - } - - static int returnType(lua_State* L, const std::vector& vector) - { - lua_newtable(L); - int table_idx = lua_gettop(L); - int n = 1; - for(const auto& val : vector) - { - int nr_vals = convert::returnType(L, val); - if (nr_vals > 1) - { - LOG(WARNING) << "std::vector<" << typeid(val).name() << "> does not support types that return multiple values. Only the first one is used."; - lua_pop(L, nr_vals - 1); - } - if (nr_vals == 0) - { - LOG(WARNING) << "No value added to the stack for std::vector<" << typeid(val).name() << ">. Adding a nil value."; - lua_pushnil(L); - } - lua_seti(L, table_idx, n++); - } - return 1; - } -}; -/* Convert parameters to std::map objects. */ -template -struct convert> -{ - static int returnType(lua_State* L, const std::map& map) - { - lua_newtable(L); - int table_idx = lua_gettop(L); - for(const auto& kv : map) - { - int nr_vals = convert::returnType(L, kv.second); - if (nr_vals > 1) - { - LOG(WARNING) << "std::map does not support types that return multiple values. Only the first one is used."; - lua_pop(L, nr_vals - 1); - } - if (nr_vals == 0) - { - LOG(WARNING) << "No value added to the stack for std::map. Adding a nil value."; - lua_pushnil(L); - } - lua_setfield(L, table_idx, kv.first.c_str()); - } - return 1; - } -}; - -template struct call -{ -}; - -class ScriptCallback; -class ScriptObject; -template struct call -{ - typedef P* PT; - typedef ScriptCallback T::* CallbackProto; - - static int setcallbackFunction(lua_State* L) - { - //Check if the parameter is a function. - luaL_checktype(L, 2, LUA_TFUNCTION); - //Check if this function is a lua function, with an reference to the environment. - // (We need the environment reference to see if the script to which the function belongs is destroyed when calling the callback) - if (lua_iscfunction(L, 2)) - luaL_error(L, "Cannot set a binding as callback function."); - lua_getupvalue(L, 2, 1); - if (!lua_istable(L, -1)) - luaL_error(L, "??[setcallbackFunction] Upvalue 1 of function is not a table..."); - lua_pushstring(L, "__script_pointer"); - lua_rawget(L, -2); - if (!lua_islightuserdata(L, -1)) - luaL_error(L, "??[setcallbackFunction] Cannot find reference back to script..."); - //Stack is now: [function_environment] [pointer] - - CallbackProto* callback_ptr = reinterpret_cast(lua_touserdata(L, lua_upvalueindex (1))); - CallbackProto callback = *callback_ptr; - T* obj = NULL; - int idx = 1; - convert< T* >::param(L, idx, obj); - if (obj) - { - ScriptCallback* callback_object = &((*obj).*(callback)); - lua_pushlightuserdata(L, callback_object); - lua_gettable(L, LUA_REGISTRYINDEX); - //Get the table which matches this callback object. If there is no table, create it. - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - lua_newtable(L); - lua_pushlightuserdata(L, callback_object); - lua_pushvalue(L, -2); - lua_settable(L, LUA_REGISTRYINDEX); - } - //The table at [-1] contains a list of callbacks. - //Stack is now [function_environment] [pointer] [callback_table] - - int callback_count = luaL_len(L, -1); - lua_pushnumber(L, callback_count + 1); - //Push a new table on the stack, store the pointer to the script object and the function in there. - lua_newtable(L); - lua_pushstring(L, "script_pointer"); - lua_pushvalue(L, -5); - lua_settable(L, -3); - lua_pushstring(L, "function"); - lua_pushvalue(L, 2); - lua_settable(L, -3); - - //Stack is now [function_environment] [pointer] [callback_table] [callback_index] [this_callback_table] - //Push the new callback table in the list of callbacks. - lua_settable(L, -3); - - lua_pop(L, 3); - - //Put the object on the stack again. - lua_pushvalue(L, 1); - return 1; - } - return 0; - } -}; - -namespace call_details -{ - template - struct defaultReturn - { - static int value(lua_State*) - { - return 0; - } - }; - - template<> - struct defaultReturn - { - static int value(lua_State* L) - { - lua_pushvalue(L, 1); - return 1; - } - }; - // Iterate over the tuple, and calls convert<> on each of its item. - template - void convertParametersImpl(lua_State* L, int& idx, Tuple& params, std::index_sequence) - { - ((convert>::param(L, idx, std::get(params))), ...); - } - - // Helper function to run conversion on a tuple. - template - void convertParameters(lua_State* L, int& idx, std::tuple& params) - { - convertParametersImpl(L, idx, params, std::index_sequence_for{}); - } - - // Execute a function by unpacking a tuple into the function's parameters. - template - auto executeFunctionImpl(T* obj, Function&& func, Tuple& params, std::index_sequence) - { - return (obj->*func)(std::get(params)...); - } - - // Helper function to execute a function passing a list of arguments into a tuple. - template - auto executeFunction(T* obj, Function&& func, std::tuple& params) - { - return executeFunctionImpl(obj, std::forward(func), params, std::index_sequence_for{}); - } - - template - auto callFunction(lua_State* L) - { - Function* func_ptr = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); - Function func = *func_ptr; - std::tuple>...> params; - T* obj = nullptr; - int idx = 1; - - convert::param(L, idx, obj); - convertParameters(L, idx, params); - if (obj) - { - if constexpr (!std::is_void_v) - return convert::returnType(L, executeFunction(obj, func, params)); - else - executeFunction(obj, func, params); - } - - return defaultReturn::value(L); - } -} - - -template struct call -{ - using FuncProto = R(T::*)(Args...); - - static int function(lua_State* L) - { - return call_details::callFunction(L); - } -}; - -template struct call -{ - using FuncProto = R(T::*)(Args...) const; - - static int function(lua_State* L) - { - return call_details::callFunction(L); - } -}; - -template class scriptBindObject -{ -public: - static const char* objectTypeName; - static const char* objectBaseTypeName; - typedef P* PT; - - static int gc_collect(lua_State* L) - { - if (!lua_istable(L, -1)) - return 0; - lua_pushstring(L, "__ptr"); - lua_rawget(L, -2); - if (lua_isuserdata(L, -1)) //When a subclass is destroyed, it's metatable might call the __gc function on it's sub-metatable. So we can get nil values here, ignore that. - { - PT* p = static_cast< PT* >(lua_touserdata(L, -1)); - if (*p) { - delete *p; - *p = nullptr; - } - } - lua_pop(L, 1); - return 0; - } - - static int create(lua_State* L) - { - P ptr = new T(); - - if (convert< P >::returnType(L, ptr)) - return 1; - LOG(ERROR) << "Failed to register pointer in script when creating an object..."; - ptr->destroy(); - return 0; - } - - static int no_create(lua_State* L) - { - return 0; - } - - static int isValid(lua_State* L) - { - T* obj = NULL; - int idx = 1; - convert::param(L, idx, obj); - lua_pushboolean(L, obj != NULL); - return 1; - } - - static int destroy(lua_State* L) - { - T* obj = NULL; - int idx = 1; - convert::param(L, idx, obj); - - if (obj != NULL) - obj->destroy(); - return 0; - } - - static void registerObjectCreation(lua_State* L) - { - lua_pushcfunction(L, create); - lua_setglobal(L, objectTypeName); - registerObject(L); - } - - static void registerObjectNoCreation(lua_State* L) - { - lua_pushcfunction(L, no_create); - lua_setglobal(L, objectTypeName); - registerObject(L); - } - - static bool checkObjectType(P obj) - { - return bool(P(obj)); - } - - static void registerObject(lua_State* L) - { - luaL_getmetatable(L, objectTypeName); - int metatable = lua_gettop(L); - if (lua_isnil(L, metatable)) - { - lua_pop(L, 1); - luaL_newmetatable(L, objectTypeName); - } - - lua_pushstring(L, "__metatable"); // protect the metatable - lua_pushstring(L, "sandbox"); - lua_settable(L, metatable); - - lua_pushstring(L, "__gc"); - lua_pushcfunction(L, gc_collect); - lua_settable(L, metatable); - - lua_pushstring(L, "__index"); - lua_newtable(L); - int functionTable = lua_gettop(L); - registerFunctions(L, functionTable); - - lua_pushstring(L, "isValid"); - lua_pushcclosure(L, isValid, 0); - lua_settable(L, functionTable); - - lua_pushstring(L, "typeName"); - lua_pushstring(L, objectTypeName); - lua_settable(L, functionTable); - - lua_pushstring(L, "destroy"); - lua_pushcclosure(L, destroy, 0); - lua_settable(L, functionTable); - - if (objectBaseTypeName != NULL) - { - luaL_getmetatable(L, objectBaseTypeName); - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - luaL_newmetatable(L, objectBaseTypeName); - } - lua_setmetatable(L, functionTable);//Set the metatable of the __index table to the base class - } - - lua_settable(L, metatable);//Set the __index value in the metatable - - lua_pop(L, 1); - } - - static void registerFunctions(lua_State* L, int table); - - template - static void addFunction(lua_State* L, int table, const char* functionName, FuncProto func) - { - lua_pushstring(L, functionName); - FuncProto* ptr = reinterpret_cast(lua_newuserdata(L, sizeof(FuncProto))); - *ptr = func; - /// If the following line gives a compiler error, then the function you are registering with - /// REGISTER_SCRIPT_CLASS_FUNCTION has: - /// * A wrong class given with it (you should give the base class of the function, not a sub class) - /// * No call handler for the parameter/return type. - lua_CFunction fptr = &call::function; - lua_pushcclosure(L, fptr, 1); - lua_settable(L, table); - } - - template - static void addCallback(lua_State* L, int table, const char* functionName, FuncProto func) - { - lua_pushstring(L, functionName); - FuncProto* ptr = reinterpret_cast(lua_newuserdata(L, sizeof(FuncProto))); - *ptr = func; - /// If the following line gives a compiler error, then the function you are registering with - /// REGISTER_SCRIPT_CLASS_FUNCTION has: - /// * A wrong class given with it (you should give the base class of the function, not a sub class) - /// * No call handler for the parameter/return type. - lua_CFunction fptr = &call::setcallbackFunction; - lua_pushcclosure(L, fptr, 1); - lua_settable(L, table); - } -}; - -#define REGISTER_SCRIPT_CLASS(T) \ - template <> const char* scriptBindObject::objectTypeName = # T; \ - template <> const char* scriptBindObject::objectBaseTypeName = NULL; \ - ScriptClassInfo scriptClassInfo ## T ( # T , "" , scriptBindObject::registerObjectCreation, scriptBindObject::checkObjectType); \ - template <> void scriptBindObject::registerFunctions(lua_State* L, int table) -#define REGISTER_SCRIPT_CLASS_NO_CREATE(T) \ - template <> const char* scriptBindObject::objectTypeName = # T; \ - template <> const char* scriptBindObject::objectBaseTypeName = NULL; \ - ScriptClassInfo scriptClassInfo ## T ( # T , "" , scriptBindObject::registerObjectNoCreation, scriptBindObject::checkObjectType); \ - template <> void scriptBindObject::registerFunctions(lua_State* L, int table) -#define REGISTER_SCRIPT_SUBCLASS(T, BASE) \ - template <> const char* scriptBindObject::objectTypeName = # T; \ - template <> const char* scriptBindObject::objectBaseTypeName = # BASE; \ - ScriptClassInfo scriptClassInfo ## T ( # T , # BASE , scriptBindObject::registerObjectCreation, scriptBindObject::checkObjectType); \ - template <> void scriptBindObject::registerFunctions(lua_State* L, int table) -#define REGISTER_SCRIPT_SUBCLASS_NO_CREATE(T, BASE) \ - template <> const char* scriptBindObject::objectTypeName = # T; \ - template <> const char* scriptBindObject::objectBaseTypeName = # BASE; \ - ScriptClassInfo scriptClassInfo ## T ( # T , # BASE , scriptBindObject::registerObjectNoCreation, scriptBindObject::checkObjectType); \ - template <> void scriptBindObject::registerFunctions(lua_State* L, int table) -#define REGISTER_SCRIPT_CLASS_FUNCTION(T, F) \ - addFunction (L, table, # F , &T::F) -#define REGISTER_SCRIPT_CLASS_CALLBACK(T, C) \ - addCallback (L, table, # C , &T::C) -#define REGISTER_SCRIPT_FUNCTION(F) \ - static void registerFunctionFunction ## F (lua_State* L) { \ - lua_pushvalue(L, -1); \ - lua_pushcclosure(L, &F, 1); \ - lua_setglobal(L, # F ); \ - } \ - ScriptClassInfo scriptClassInfo ## F ( # F , "" , registerFunctionFunction ## F , NULL ); - -#define REGISTER_SCRIPT_FUNCTION_NAMED(F, NAME) \ - static void registerFunctionFunction ## F (lua_State* L) { \ - lua_pushvalue(L, -1); \ - lua_pushcclosure(L, &F, 1); \ - lua_setglobal(L, NAME ); \ - } \ - ScriptClassInfo scriptClassInfo ## F ( NAME , "" , registerFunctionFunction ## F , NULL ); - -#endif//SCRIPT_INTERFACE_MAGIC_H diff --git a/src/scriptInterfaceSandbox.cpp b/src/scriptInterfaceSandbox.cpp deleted file mode 100644 index 248abead..00000000 --- a/src/scriptInterfaceSandbox.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "lua/lua.hpp" -#include "scriptInterfaceSandbox.h" - -// Add or protect a metatable for the provided object -// Input Lua stack: [object] -// Output Lua stack: [object] -void protectLuaMetatable(lua_State* L) -{ - if (!lua_getmetatable(L, -1)) { - lua_newtable(L); // [object] [mt] - lua_pushvalue(L, -1); // [object] [mt] [mt] - lua_setmetatable(L, -3); // [object] [mt] - } - - lua_pushstring(L, "__metatable"); // [object] [mt] "__metatable" - lua_pushstring(L, "sandbox"); // [object] [mt] "__metatable" "sandbox" - lua_rawset(L, -3); // [object] [mt] - lua_pop(L, 1); // [object] -} diff --git a/src/scriptInterfaceSandbox.h b/src/scriptInterfaceSandbox.h deleted file mode 100644 index b1e680c3..00000000 --- a/src/scriptInterfaceSandbox.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef SCRIPT_INTERFACE_SANDBOX_H -#define SCRIPT_INTERFACE_SANDBOX_H - -#include "lua/lua.hpp" - -// Add or protect a metatable for the provided object such that the metatable can't be read or changed from inside the sandbox. -// Input Lua stack: [object] -// Output Lua stack: [object] -void protectLuaMetatable(lua_State* L); - -#endif // SCRIPT_INTERFACE_SANDBOX_H diff --git a/src/systems/collision.cpp b/src/systems/collision.cpp new file mode 100644 index 00000000..6d943187 --- /dev/null +++ b/src/systems/collision.cpp @@ -0,0 +1,209 @@ +#include "systems/collision.h" +#include "components/collision.h" +#include "ecs/query.h" +#include "engine.h" +#include "random.h" + +#include +#include + + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-override" +#endif//__GNUC__ +#include "Box2D/Box2D.h" +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif//__GNUC__ + + +#define BOX2D_SCALE 20.0f +static inline glm::vec2 b2v(b2Vec2 v) +{ + return glm::vec2(v.x * BOX2D_SCALE, v.y * BOX2D_SCALE); +} +static inline b2Vec2 v2b(glm::vec2 v) +{ + return b2Vec2(v.x / BOX2D_SCALE, v.y / BOX2D_SCALE); +} + + +static b2World* world; + + +namespace sp { + +std::vector CollisionSystem::handlers; + +namespace { +struct Collision +{ + sp::ecs::Entity A; + sp::ecs::Entity B; + float collision_force; +}; +} + +void CollisionSystem::update(float delta) +{ + if (!world) + world = new b2World(b2Vec2(0, 0)); + if (delta <= 0.0f) + return; + + // Go over each entity with physics, and create/update bodies if needed. + for(auto [entity, transform, physics] : sp::ecs::Query()) { + if (physics.physics_dirty) + { + physics.physics_dirty = false; + sp::ecs::Entity* ptr; + if (physics.body) { + ptr = (sp::ecs::Entity*)physics.body->GetUserData(); + world->DestroyBody(physics.body); + } else { + ptr = new sp::ecs::Entity(); + *ptr = entity; + } + + b2BodyDef bodyDef; + bodyDef.type = physics.type == Physics::Type::Dynamic ? b2_dynamicBody : b2_kinematicBody; + bodyDef.userData = ptr; + bodyDef.allowSleep = false; + bodyDef.position = v2b(transform.position); + bodyDef.angle = glm::radians(transform.rotation); + physics.body = world->CreateBody(&bodyDef); + + b2FixtureDef shapeDef; + shapeDef.density = 1.f; + shapeDef.friction = 0.f; + shapeDef.isSensor = physics.type == Physics::Type::Sensor; + + if (physics.shape == Physics::Shape::Circle) { + b2CircleShape shape; + shape.m_radius = physics.size.x / BOX2D_SCALE; + shapeDef.shape = &shape; + physics.body->CreateFixture(&shapeDef); + } else { + b2PolygonShape shape; + shape.SetAsBox(physics.size.x / 2.f / BOX2D_SCALE, physics.size.y / 2.f / BOX2D_SCALE, {0, 0}, 0); + shapeDef.shape = &shape; + physics.body->CreateFixture(&shapeDef); + } + } + if (transform.position_user_set && physics.body) { + physics.body->SetTransform(v2b(transform.position), physics.body->GetAngle()); + transform.position_user_set = false; + } + if (transform.rotation_user_set && physics.body) { + physics.body->SetTransform(physics.body->GetPosition(), glm::radians(transform.rotation)); + transform.rotation_user_set = false; + } + if (physics.linear_velocity_user_set && physics.body) { + physics.body->SetLinearVelocity(v2b(physics.linear_velocity)); + physics.linear_velocity_user_set = false; + } + if (physics.angular_velocity_user_set && physics.body) { + physics.body->SetAngularVelocity(glm::radians(physics.angular_velocity)); + physics.angular_velocity_user_set = false; + } + } + + world->Step(delta, 4, 8); + + // Go over each body in the physics world, and update the entity, or delete the body if the entity is gone. + auto now = engine->getElapsedTime(); + std::vector remove_list; + for(b2Body* body = world->GetBodyList(); body; body = body->GetNext()) { + sp::ecs::Entity* entity_ptr = (sp::ecs::Entity*)body->GetUserData(); + Transform* transform; + Physics* physics = nullptr; + if (!*entity_ptr || !(physics = entity_ptr->getComponent()) || !(transform = entity_ptr->getComponent())) { + delete entity_ptr; + remove_list.push_back(body); + if (physics) { + // if we have a physics component (thus only missing transform), set it so + // that we'll recreate the body if the entity gets a transform again later + physics->physics_dirty = true; + physics->body = nullptr; + } + } else { + transform->position = b2v(body->GetPosition()); + transform->rotation = glm::degrees(body->GetAngle()); + physics->linear_velocity = b2v(body->GetLinearVelocity()); + physics->angular_velocity = glm::degrees(body->GetAngularVelocity()); + + auto position_delta = glm::length(transform->position - transform->last_send_position); + auto rotation_delta = std::abs(transform->rotation - transform->last_send_rotation); + auto time_between_updates = 1.0f - position_delta / 200.0f - rotation_delta / 100.0f; + if (position_delta == 0.0f) + time_between_updates += random(0.0f, 5.0f); + if (time_between_updates < 0.05f) + time_between_updates = 0.05f; + if (transform->last_send_time + time_between_updates < now) + transform->multiplayer_dirty = true; + } + } + for(auto body : remove_list) { + world->DestroyBody(body); + } + + // Find all the collisions and process them. + std::vector collisions; + for(b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext()) + { + if (contact->IsTouching() && contact->IsEnabled()) + { + float force = 0.0f; + for (int n = 0; n < contact->GetManifold()->pointCount; n++) + { + force += contact->GetManifold()->points[n].normalImpulse * BOX2D_SCALE; + } + auto a = (sp::ecs::Entity*)contact->GetFixtureA()->GetBody()->GetUserData(); + auto b = (sp::ecs::Entity*)contact->GetFixtureB()->GetBody()->GetUserData(); + + for(auto handler : handlers) + { + if (!*a || !*b) + break; + handler->collision(*a, *b, force); + if (!*a || !*b) + break; + handler->collision(*b, *a, force); + } + } + } +} + +class QueryCallback : public b2QueryCallback +{ +public: + std::vector list; + + /// Called for each fixture found in the query AABB. + /// @return false to terminate the query. + virtual bool ReportFixture(b2Fixture* fixture) override + { + auto ptr = (sp::ecs::Entity*)fixture->GetBody()->GetUserData(); + if (*ptr) + list.push_back(*ptr); + return true; + } +}; + +std::vector CollisionSystem::queryArea(glm::vec2 lowerBound, glm::vec2 upperBound) +{ + QueryCallback callback; + b2AABB aabb; + aabb.lowerBound = v2b(lowerBound); + aabb.upperBound = v2b(upperBound); + if (aabb.lowerBound.x > aabb.upperBound.x) + std::swap(aabb.upperBound.x, aabb.lowerBound.x); + if (aabb.lowerBound.y > aabb.upperBound.y) + std::swap(aabb.upperBound.y, aabb.lowerBound.y); + if (world) + world->QueryAABB(&callback, aabb); + return callback.list; +} + +} \ No newline at end of file diff --git a/src/systems/collision.h b/src/systems/collision.h new file mode 100644 index 00000000..9b8000ca --- /dev/null +++ b/src/systems/collision.h @@ -0,0 +1,26 @@ +#pragma once + +#include "ecs/entity.h" +#include + + +namespace sp { + +class CollisionHandler +{ +public: + virtual void collision(sp::ecs::Entity a, sp::ecs::Entity b, float force) = 0; +}; + +class CollisionSystem +{ +public: + static void update(float delta); + static void addHandler(CollisionHandler* handler) { handlers.push_back(handler); } + + static std::vector queryArea(glm::vec2 lowerBound, glm::vec2 upperBound); +private: + static std::vector handlers; +}; + +} \ No newline at end of file diff --git a/src/windowManager.cpp b/src/windowManager.cpp index 2196c3ff..b9cff8c7 100644 --- a/src/windowManager.cpp +++ b/src/windowManager.cpp @@ -10,7 +10,6 @@ #include "graphics/opengl.h" #include "Updatable.h" #include "Renderable.h" -#include "collisionable.h" #include "postProcessManager.h" #include "io/keybinding.h"