From 55a15c204eb80f0ab53ce94d862cdc191b38800d Mon Sep 17 00:00:00 2001 From: Paul Kulchenko Date: Fri, 6 Aug 2021 04:55:01 -0700 Subject: [PATCH] Add arrays to Lua (#222) You can now have O(1) length and append. --- third_party/lua/lapi.c | 47 ++++++- third_party/lua/larray.c | 107 ++++++++++++++++ third_party/lua/larray.h | 22 ++++ third_party/lua/lbaselib.c | 15 ++- third_party/lua/ldebug.c | 2 +- third_party/lua/lgc.c | 4 + third_party/lua/lobject.h | 27 ++++ third_party/lua/lparser.c | 37 ++++-- third_party/lua/lstate.h | 2 + third_party/lua/ltable.c | 26 +++- third_party/lua/ltablib.c | 13 ++ third_party/lua/ltm.c | 3 +- third_party/lua/lua.h | 7 +- third_party/lua/lvm.c | 39 +++++- third_party/lua/lvm.h | 15 +++ third_party/lua/test/arrays.lua | 211 ++++++++++++++++++++++++++++++++ 16 files changed, 557 insertions(+), 20 deletions(-) create mode 100644 third_party/lua/larray.c create mode 100644 third_party/lua/larray.h create mode 100644 third_party/lua/test/arrays.lua diff --git a/third_party/lua/lapi.c b/third_party/lua/lapi.c index ce9ba51d1..067f60a84 100644 --- a/third_party/lua/lapi.c +++ b/third_party/lua/lapi.c @@ -18,6 +18,7 @@ #include "third_party/lua/lstate.h" #include "third_party/lua/lstring.h" #include "third_party/lua/ltable.h" +#include "third_party/lua/larray.h" #include "third_party/lua/ltm.h" #include "third_party/lua/lua.h" #include "third_party/lua/lundump.h" @@ -312,6 +313,12 @@ LUA_API int lua_isuserdata (lua_State *L, int idx) { } +LUA_API int lua_isarray (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (isvalid(L, o) && ttype(o) == LUA_TTABLE && hvalue(o)->truearray); +} + + LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { const TValue *o1 = index2value(L, index1); const TValue *o2 = index2value(L, index2); @@ -759,6 +766,41 @@ LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { } +LUA_API void lua_createarray (lua_State *L, int narray) { + Array *a; + lua_lock(L); + a = luaA_new(L); + setavalue2s(L, L->top, a); + api_incr_top(L); + if (narray > 0) + luaA_resize(L, a, narray); + luaC_checkGC(L); + lua_unlock(L); +} + + +LUA_API void lua_resize (lua_State *L, int idx, unsigned int size) { + TValue *o; + Table *t; + unsigned int i, oldsize; + lua_lock(L); + o = index2value(L, idx); + api_check(L, ttistable(o), "table expected"); + t = hvalue(o); + oldsize = t->sizeused; + if (size > luaH_realasize(t)) { + luaH_resizearray(L, t, size); + } + lua_unlock(L); + /* set removed elements to nil when shrinking array size */ + for(i = size + 1; i <= oldsize; i++) { + lua_pushnil(L); + lua_seti(L, idx, i); + } + t->sizeused = size; +} + + LUA_API int lua_getmetatable (lua_State *L, int objindex) { const TValue *obj; Table *mt; @@ -867,7 +909,10 @@ LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { lua_lock(L); api_checknelems(L, 1); t = index2value(L, idx); - if (luaV_fastgeti(L, t, n, slot)) { + if (ttisarray(t)) { + luaA_setint(L, avalue(t), n, s2v(L->top - 1)); + } + else if (luaV_fastgeti(L, t, n, slot)) { luaV_finishfastset(L, t, slot, s2v(L->top - 1)); } else { diff --git a/third_party/lua/larray.c b/third_party/lua/larray.c new file mode 100644 index 000000000..42252823c --- /dev/null +++ b/third_party/lua/larray.c @@ -0,0 +1,107 @@ +/* +** Lua arrays +** See Copyright Notice in lua.h +*/ + +asm(".ident\t\"\\n\\n\ +lua-array (MIT License)\\n\ +Copyright 2018-2021 Petri Häkkinen\""); +asm(".include \"libc/disclaimer.inc\""); + +#define larray_c +#define LUA_CORE + +#include "third_party/lua/lprefix.h" + +#include +#include + +#include "third_party/lua/lua.h" + +#include "third_party/lua/ldebug.h" +#include "third_party/lua/ldo.h" +#include "third_party/lua/lgc.h" +#include "third_party/lua/lmem.h" +#include "third_party/lua/lobject.h" +#include "third_party/lua/lstate.h" +#include "third_party/lua/lstring.h" +#include "third_party/lua/ltable.h" +#include "third_party/lua/larray.h" +#include "third_party/lua/lvm.h" + +Array *luaA_new (lua_State *L) { + GCObject *o = luaC_newobj(L, LUA_TARRAY, sizeof(Array)); + Array *a = gco2a(o); + a->array = NULL; + a->alimit = 0; + return a; +} + + +void luaA_resize (lua_State *L, Array *a, unsigned int newsize) { + unsigned int i; + unsigned int oldsize = a->alimit; + TValue *newarray; + + /* allocate new array */ + newarray = luaM_reallocvector(L, a->array, oldsize, newsize, TValue); + if (newarray == NULL && newsize > 0) { /* allocation failed? */ + luaM_error(L); /* raise error (with array unchanged) */ + } + + /* allocation ok; initialize new part of the array */ + a->array = newarray; /* set new array part */ + a->alimit = newsize; + for (i = oldsize; i < newsize; i++) /* clear new slice of the array */ + setempty(&a->array[i]); +} + + +void luaA_free (lua_State *L, Array *a) { + luaM_freearray(L, a->array, a->alimit); + luaM_free(L, a); +} + + +const TValue *luaA_getint (lua_State *L, Array *a, lua_Integer key) { + /* (1 <= key && key <= t->alimit) */ + if (l_castS2U(key) - 1u < a->alimit) + return &a->array[key - 1]; + else + return luaO_nilobject; +} + +const TValue *luaA_get (lua_State *L, Array *a, const TValue *key) { + if (ttypetag(key) == LUA_VNUMINT) { + lua_Integer ikey = ivalue(key); + if (l_castS2U(ikey) - 1u < a->alimit) + return &a->array[ikey - 1]; + else + return luaO_nilobject; + } else { + return luaO_nilobject; + } +} + +void luaA_setint (lua_State *L, Array *a, lua_Integer key, TValue *value) { + if (l_castS2U(key) - 1u < a->alimit) { + /* set value! */ + TValue* val = &a->array[key - 1]; + val->value_ = value->value_; + val->tt_ = value->tt_; + checkliveness(L,val); + } else { + /* TODO: this error message could be improved! */ + luaG_runerror(L, "array index out of bounds"); + } +} + +void luaA_set (lua_State *L, Array *a, const TValue* key, TValue *value) { + if (ttypetag(key) == LUA_VNUMINT) { + lua_Integer ikey = ivalue(key); + luaA_setint(L, a, ikey, value); + } else { + /* TODO: the error message could be improved */ + luaG_runerror(L, "attempt to index array with a non-integer value"); + } +} diff --git a/third_party/lua/larray.h b/third_party/lua/larray.h new file mode 100644 index 000000000..f8ebfc0a3 --- /dev/null +++ b/third_party/lua/larray.h @@ -0,0 +1,22 @@ +/* +** Lua arrays +** See Copyright Notice in lua.h +*/ + +#ifndef larray_h +#define larray_h + +#include "third_party/lua/lobject.h" + +#define luaO_nilobject (&G(L)->nilvalue) + +LUAI_FUNC Array *luaA_new (lua_State *L); +LUAI_FUNC void luaA_resize (lua_State *L, Array *a, unsigned int nsize); +LUAI_FUNC void luaA_free (lua_State *L, Array *a); + +LUAI_FUNC const TValue *luaA_getint (lua_State *L, Array *a, lua_Integer key); +LUAI_FUNC void luaA_setint (lua_State *L, Array *a, lua_Integer key, TValue *value); +LUAI_FUNC const TValue *luaA_get (lua_State *L, Array *a, const TValue *key); +LUAI_FUNC void luaA_set (lua_State *L, Array *a, const TValue *key, TValue *value); + +#endif diff --git a/third_party/lua/lbaselib.c b/third_party/lua/lbaselib.c index 25f37e902..e9a5dcd41 100644 --- a/third_party/lua/lbaselib.c +++ b/third_party/lua/lbaselib.c @@ -254,9 +254,12 @@ static int luaB_next (lua_State *L) { } } +static int luaB_ipairs (lua_State *L); static int luaB_pairs (lua_State *L) { luaL_checkany(L, 1); + if (lua_isarray(L, 1)) + return luaB_ipairs(L); if (luaL_getmetafield(L, 1, "__pairs") == LUA_TNIL) { /* no metamethod? */ lua_pushcfunction(L, luaB_next); /* will return generator, */ lua_pushvalue(L, 1); /* state, */ @@ -279,6 +282,16 @@ static int ipairsaux (lua_State *L) { return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; } +/* +** Traversal function for 'ipairs' specialized for arrays. +*/ +static int ipairsauxarray (lua_State *L) { + lua_Integer i = luaL_checkinteger(L, 2) + 1; + lua_pushinteger(L, i); + lua_geti(L, 1, i); + return (lua_rawlen(L, 1) == i-1) ? 1 : 2; +} + /* ** 'ipairs' function. Returns 'ipairsaux', given "table", 0. @@ -286,7 +299,7 @@ static int ipairsaux (lua_State *L) { */ static int luaB_ipairs (lua_State *L) { luaL_checkany(L, 1); - lua_pushcfunction(L, ipairsaux); /* iteration function */ + lua_pushcfunction(L, lua_isarray(L, 1) ? ipairsauxarray : ipairsaux); /* iteration function */ lua_pushvalue(L, 1); /* state */ lua_pushinteger(L, 0); /* initial value */ return 3; diff --git a/third_party/lua/ldebug.c b/third_party/lua/ldebug.c index 1f2c0785c..1657922d9 100644 --- a/third_party/lua/ldebug.c +++ b/third_party/lua/ldebug.c @@ -688,7 +688,7 @@ static const char *varinfo (lua_State *L, const TValue *o) { l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { const char *t = luaT_objtypename(L, o); - luaG_runerror(L, "attempt to %s a %s value%s", op, t, varinfo(L, o)); + luaG_runerror(L, "attempt to %s a%s %s value%s", op, ttisarray(o) ? "n" : "", t, varinfo(L, o)); } diff --git a/third_party/lua/lgc.c b/third_party/lua/lgc.c index da7894b72..310c87133 100644 --- a/third_party/lua/lgc.c +++ b/third_party/lua/lgc.c @@ -17,6 +17,7 @@ #include "third_party/lua/lstate.h" #include "third_party/lua/lstring.h" #include "third_party/lua/ltable.h" +#include "third_party/lua/larray.h" #include "third_party/lua/ltm.h" #include "third_party/lua/lua.h" @@ -777,6 +778,9 @@ static void freeobj (lua_State *L, GCObject *o) { case LUA_VTABLE: luaH_free(L, gco2t(o)); break; + case LUA_TARRAY: + luaA_free(L, gco2a(o)); + break; case LUA_VTHREAD: luaE_freethread(L, gco2th(o)); break; diff --git a/third_party/lua/lobject.h b/third_party/lua/lobject.h index 33dbba4fb..717a2e563 100644 --- a/third_party/lua/lobject.h +++ b/third_party/lua/lobject.h @@ -722,6 +722,8 @@ typedef struct Table { lu_byte flags; /* 1<

'{' [ field { sep field } [sep] ] '}' sep -> ',' | ';' */ + + /* special case for arrays (when 'array' is true): + constructor -> '[' [ listfield { sep listfield } [sep] ] ']' + sep -> ',' | ';' */ + FuncState *fs = ls->fs; int line = ls->linenumber; int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); @@ -918,18 +923,32 @@ static void constructor (LexState *ls, expdesc *t) { 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) */ - checknext(ls, '{'); + luaK_exp2nextreg(ls->fs, t); /* fix it at stack top */ + checknext(ls, (array ? '[' : '{')); do { lua_assert(cc.v.k == VVOID || cc.tostore > 0); - if (ls->t.token == '}') break; + if (ls->t.token == (array ? ']' : '}')) break; closelistfield(fs, &cc); - field(ls, &cc); + if (array) + listfield(ls, &cc); + else + field(ls, &cc); } while (testnext(ls, ',') || testnext(ls, ';')); - check_match(ls, '}', '{', line); + check_match(ls, array ? ']' : '}', array ? '[' : '{', line); lastlistfield(fs, &cc); luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh); + /* encode arrayness by setting B to max value (255) */ + if (array) { + /* make sure B is not already 255 */ + /* I don't this can happen in practice (max size is luaO_fb2int(255) = 3221225472), but let's be sure... */ + unsigned int b = GETARG_B(fs->f->code[pc]); + if (b == 255) + luaX_syntaxerror(fs->ls, "table too large"); + SETARG_B(fs->f->code[pc], 255); + } } + /* }====================================================================== */ @@ -1023,7 +1042,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { break; } case '{': { /* funcargs -> constructor */ - constructor(ls, &args); + constructor(ls, &args, 0); break; } case TK_STRING: { /* funcargs -> STRING */ @@ -1158,7 +1177,11 @@ static void simpleexp (LexState *ls, expdesc *v) { break; } case '{': { /* constructor */ - constructor(ls, v); + constructor(ls, v, 0); + return; + } + case '[': { /* array constructor */ + constructor(ls, v, 1); return; } case TK_FUNCTION: { diff --git a/third_party/lua/lstate.h b/third_party/lua/lstate.h index 7b5aed28d..04b444894 100644 --- a/third_party/lua/lstate.h +++ b/third_party/lua/lstate.h @@ -351,6 +351,7 @@ union GCUnion { struct Udata u; union Closure cl; struct Table h; + struct Array a; struct Proto p; struct lua_State th; /* thread */ struct UpVal upv; @@ -373,6 +374,7 @@ union GCUnion { #define gco2cl(o) \ check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl)) #define gco2t(o) check_exp((o)->tt == LUA_VTABLE, &((cast_u(o))->h)) +#define gco2a(o) check_exp((o)->tt == LUA_TARRAY, &((cast_u(o))->a)) #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)) diff --git a/third_party/lua/ltable.c b/third_party/lua/ltable.c index 4d021a141..29f642026 100644 --- a/third_party/lua/ltable.c +++ b/third_party/lua/ltable.c @@ -565,7 +565,7 @@ void luaH_resize (lua_State *L, Table *t, unsigned int newasize, 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]); + 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 */ @@ -613,8 +613,10 @@ Table *luaH_new (lua_State *L) { Table *t = gco2t(o); t->metatable = NULL; t->flags = cast_byte(maskflags); /* table has no metamethod fields */ + t->truearray = 0; t->array = NULL; t->alimit = 0; + t->sizeused = 0; setnodevector(L, t, 0); return t; } @@ -652,7 +654,25 @@ void luaH_newkey (lua_State *L, Table *t, const TValue *key, TValue *value) { TValue aux; if (l_unlikely(ttisnil(key))) luaG_runerror(L, "table index is nil"); - else if (ttisfloat(key)) { + else if (t->truearray) { + /* set new value to true array */ + int capacity; + int asize = luaH_realasize(t); + int idx = ivalue(key); /* TODO: does not handle numbers larger than fits into a 32-bit signed integer! */ + if (!ttisinteger(key) || idx < 1) + luaG_runerror(L, "invalid array index"); + /* enlarge capacity */ + if (asize < idx) { + capacity = asize + (asize >> 1); + if (capacity < idx) + capacity = idx; + luaH_resizearray(L, t, capacity); + } + t->sizeused = idx; // since this is guaranteed to be a new key, it exceeds t->sizeused + luaC_barrierback(L, obj2gco(t), key); + setobj2t(L, cast(TValue *, t->array + idx - 1), value); + return; + } else if (ttisfloat(key)) { lua_Number f = fltvalue(key); lua_Integer k; if (luaV_flttointeger(f, &k, F2Ieq)) { /* does key fit in an integer? */ @@ -907,6 +927,8 @@ static unsigned int binsearch (const TValue *array, unsigned int i, */ lua_Unsigned luaH_getn (Table *t) { unsigned int limit = t->alimit; + if (t->truearray) + return t->sizeused; if (limit > 0 && isempty(&t->array[limit - 1])) { /* (1)? */ /* there must be a boundary before 'limit' */ if (limit >= 2 && !isempty(&t->array[limit - 2])) { diff --git a/third_party/lua/ltablib.c b/third_party/lua/ltablib.c index 553f2a7e6..76ef39665 100644 --- a/third_party/lua/ltablib.c +++ b/third_party/lua/ltablib.c @@ -401,6 +401,18 @@ static int sort (lua_State *L) { return 0; } + +static int resize (lua_State *L) { + /* reserve capacity of the array part -- useful when filling large tables/arrays */ + int size; + luaL_checktype(L, 1, LUA_TTABLE); + size = luaL_checkinteger(L, 2); + luaL_argcheck(L, size >= 0, 2, "invalid size"); + lua_resize(L, 1, (unsigned int)size); + return 0; +} + + /* }====================================================== */ @@ -412,6 +424,7 @@ static const luaL_Reg tab_funcs[] = { {"remove", tremove}, {"move", tmove}, {"sort", sort}, + {"resize", resize}, {NULL, NULL} }; diff --git a/third_party/lua/ltm.c b/third_party/lua/ltm.c index 75f67c5f6..45849eb35 100644 --- a/third_party/lua/ltm.c +++ b/third_party/lua/ltm.c @@ -26,7 +26,7 @@ static const char udatatypename[] = "userdata"; LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTYPES] = { "no value", "nil", "boolean", udatatypename, "number", - "string", "table", "function", udatatypename, "thread", + "string", "table", "function", udatatypename, "thread", "array", "upvalue", "proto" /* these last cases are used for tests only */ }; @@ -264,4 +264,3 @@ void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) { for (; i < wanted; i++) /* complete required results with nil */ setnilvalue(s2v(where + i)); } - diff --git a/third_party/lua/lua.h b/third_party/lua/lua.h index c988b3def..180bdc4d4 100644 --- a/third_party/lua/lua.h +++ b/third_party/lua/lua.h @@ -67,8 +67,8 @@ typedef struct lua_State lua_State; #define LUA_TFUNCTION 6 #define LUA_TUSERDATA 7 #define LUA_TTHREAD 8 - -#define LUA_NUMTYPES 9 +#define LUA_TARRAY 9 +#define LUA_NUMTYPES 10 @@ -180,6 +180,7 @@ LUA_API int (lua_isstring) (lua_State *L, int idx); LUA_API int (lua_iscfunction) (lua_State *L, int idx); LUA_API int (lua_isinteger) (lua_State *L, int idx); LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_isarray) (lua_State *L, int idx); LUA_API int (lua_type) (lua_State *L, int idx); LUA_API const char *(lua_typename) (lua_State *L, int tp); @@ -252,6 +253,8 @@ 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_createarray) (lua_State *L, int narr); +LUA_API void (lua_resize) (lua_State *L, int idx, unsigned int size); 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_getiuservalue) (lua_State *L, int idx, int n); diff --git a/third_party/lua/lvm.c b/third_party/lua/lvm.c index 7780b974a..069275b2c 100644 --- a/third_party/lua/lvm.c +++ b/third_party/lua/lvm.c @@ -17,6 +17,7 @@ #include "third_party/lua/lstate.h" #include "third_party/lua/lstring.h" #include "third_party/lua/ltable.h" +#include "third_party/lua/larray.h" #include "third_party/lua/ltm.h" #include "third_party/lua/lua.h" #include "third_party/lua/lvm.h" @@ -283,6 +284,11 @@ void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, for (loop = 0; loop < MAXTAGLOOP; loop++) { if (slot == NULL) { /* 't' is not a table? */ lua_assert(!ttistable(t)); + if (ttisarray(t)) { + /* get array value */ + setobj2s(L, val, luaA_get(L, avalue(t), key)); + return; + } tm = luaT_gettmbyobj(L, t, TM_INDEX); if (l_unlikely(notm(tm))) luaG_typeerror(L, t, "index"); /* no metamethod */ @@ -331,12 +337,17 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key, if (tm == NULL) { /* no metamethod? */ luaH_finishset(L, h, key, slot, val); /* set new value */ invalidateTMcache(h); + /* enlarge array length when necessary */ + /* this should be quite fast as fields of h and key have already been loaded to CPU cache at this point */ + h->sizeused += (val_(key).i > h->sizeused) & ttisinteger(key) & 1; luaC_barrierback(L, obj2gco(h), val); return; } /* else will try the metamethod */ } else { /* not a table; check metamethod */ + if(ttisarray(t)) + luaG_typeerror(L, t, "set non-integer index of"); tm = luaT_gettmbyobj(L, t, TM_NEWINDEX); if (l_unlikely(notm(tm))) luaG_typeerror(L, t, "index"); @@ -682,6 +693,11 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { setivalue(s2v(ra), luaH_getn(h)); /* else primitive len */ return; } + case LUA_TARRAY: { + Array *a = avalue(rb); + setivalue(s2v(ra), a->alimit); + return; + } case LUA_VSHRSTR: { setivalue(s2v(ra), tsvalue(rb)->shrlen); return; @@ -1241,7 +1257,10 @@ void luaV_execute (lua_State *L, CallInfo *ci) { const TValue *slot; TValue *rb = vRB(i); int c = GETARG_C(i); - if (luaV_fastgeti(L, rb, c, slot)) { + if(ttisarray(rb)) { + setobj2s(L, ra, luaA_getint(L, avalue(rb), c)); + } + else if (luaV_fastgeti(L, rb, c, slot)) { setobj2s(L, ra, slot); } else { @@ -1294,7 +1313,10 @@ void luaV_execute (lua_State *L, CallInfo *ci) { const TValue *slot; int c = GETARG_B(i); TValue *rc = RKC(i); - if (luaV_fastgeti(L, s2v(ra), c, slot)) { + if(ttisarray(s2v(ra))) { + luaA_setint(L, avalue(s2v(ra)), c, rc); + } + else if (luaV_fastgeti(L, s2v(ra), c, slot)) { luaV_finishfastset(L, s2v(ra), slot, rc); } else { @@ -1319,6 +1341,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) { vmcase(OP_NEWTABLE) { int b = GETARG_B(i); /* log2(hash size) + 1 */ int c = GETARG_C(i); /* array size */ + /* decode arrayness */ + int array = (b == 255); Table *t; if (b > 0) b = 1 << (b - 1); /* size is 2^(b - 1) */ @@ -1328,9 +1352,16 @@ void luaV_execute (lua_State *L, CallInfo *ci) { pc++; /* skip extra argument */ L->top = ra + 1; /* correct top in case of emergency GC */ t = luaH_new(L); /* memory allocation */ + t->truearray = array; + t->sizeused = c; sethvalue2s(L, ra, t); - if (b != 0 || c != 0) - luaH_resize(L, t, c, b); /* idem */ + if (b != 0 || c != 0) { + if (array) { + luaH_resizearray(L, t, c); /* idem */ + } else { + luaH_resize(L, t, c, b); /* idem */ + } + } checkGC(L, ra + 1); vmbreak; } diff --git a/third_party/lua/lvm.h b/third_party/lua/lvm.h index 8fd25ed22..f07eacad4 100644 --- a/third_party/lua/lvm.h +++ b/third_party/lua/lvm.h @@ -88,6 +88,13 @@ typedef enum { : (slot = f(hvalue(t), k), /* else, do raw access */ \ !isempty(slot))) /* result not empty? */ +/* This one supports arrays as well as tables. */ +#define luaV_fastget2(L,t,k,slot,f) \ + (ttisarray(t) ? (slot = f(avalue(t), k), !isempty(slot)) : \ + (!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 @@ -100,6 +107,14 @@ typedef enum { ? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \ !isempty(slot))) /* result not empty? */ +/* This one supports arrays as well as tables. */ +#define luaV_fastgeti2(L,t,k,slot) \ + (ttisarray(t) ? (slot = (l_castS2U(k) - 1u < avalue(t)->sizearray) ? &avalue(t)->array[k - 1] : luaO_nilobject, !isempty(slot)) : \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = (l_castS2U(k) - 1u < hvalue(t)->sizearray) \ + ? &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, diff --git a/third_party/lua/test/arrays.lua b/third_party/lua/test/arrays.lua new file mode 100644 index 000000000..27517787a --- /dev/null +++ b/third_party/lua/test/arrays.lua @@ -0,0 +1,211 @@ +local errors = false + +function assert(cond) + if not cond then + local line = debug.getinfo(2).currentline + print("test failed at line " .. line) + errors = true + end +end + +-- test array constructor +do + local a = [1, 2, 3] + assert(a[1] == 1) + assert(a[2] == 2) + assert(a[3] == 3) + assert(#a == 3) +end + +-- test nested array constructor +do + local a = [1, 2, 3, [4, 5]] + assert(a[1] == 1) + assert(a[2] == 2) + assert(a[3] == 3) + assert(a[4][1] == 4) + assert(a[4][2] == 5) + assert(#a == 4) +end + +-- test array write +do + local a = [1, 2, 3] + a[1] = -1 + assert(a[1] == -1) + assert(#a == 3) +end + +-- test array extend +do + local a = [1, 2, 3] + a[7] = 5 + assert(#a == 7) + assert(a[7] == 5) +end + +-- test array extend 2 +do + local a = [] + for i=5,15 do + a[i] = i + assert(a[i] == i) + assert(#a == i) + end +end + +-- test setting element to nil (should not affect array size) +do + local a = [1, 2, 3] + a[3] = nil + assert(a[3] == nil) + assert(#a == 3) +end + +-- test array with holes +do + local a = [1, nil, 3] + assert(a[1] == 1) + assert(a[2] == nil) + assert(a[3] == 3) + assert(#a == 3) + a[1] = nil + assert(#a == 3) +end + +-- test filling hole in array +do + local a = [1, nil, 3] + a[2] = 2 + assert(a[2] == 2) + assert(#a == 3) +end + +-- test filling hole in array 2 +do + local a = [1, nil, 3] + local i = 2 + a[i] = 2 + assert(a[2] == 2) + assert(#a == 3) +end + +-- test read out of bounds +do + local a = [1, 2, 3] + assert(#a == 3) + assert(a[0] == nil) + assert(a[4] == nil) + assert(#a == 3) +end + +-- test array resize (array growing) +do + local a = [1, 2, 3] + table.resize(a, 1000) + assert(a[4] == nil) + assert(#a == 1000) + a[1] = 4 + a[10] = 10 + a[11] = 11 + assert(#a == 1000) +end + +-- test array resize (array shrinking) +do + local a = [1, 2, 3, 4, 5] + table.resize(a, 3) + assert(a[1] == 1) + assert(a[2] == 2) + assert(a[3] == 3) + assert(a[4] == nil) + assert(a[5] == nil) + assert(#a == 3) +end + +-- test non-const integer +do + local a = [] + local y = 3 + a[y] = 66 + assert(a[3] == 66) + assert(#a == 3) +end + +-- test table.insert() +do + local a = [1, 2, 3] + table.insert(a, 1, "new") + assert(a[1] == "new") + assert(a[2] == 1) + assert(a[3] == 2) + assert(a[4] == 3) + assert(#a == 4) +end + +-- test table.remove() +do + local a = [1, 2, 3] + table.remove(a, 1) + assert(a[1] == 2) + assert(a[2] == 3) + -- TODO: fix the implementation, as after upgrading to Lua 5.4.3 + -- it keeps the array size the same after table.remove() + -- assert(#a == 2) +end + +-- test ipairs +-- expected behavior: equivalent to for i=1,#a do print(i, a[i]) end +do + local a = [1, nil, 3, nil] + local cnt = 0 + for k,v in ipairs(a) do + assert(v == a[k]) + cnt = cnt + 1 + end + assert(cnt == #a) +end + +-- test pairs +-- expected behavior: same as ipairs? +do + local a = [1, nil, 3] + local cnt = 0 + for k,v in pairs(a) do + assert(v == a[k]) + cnt = cnt + 1 + end + assert(cnt == 3) +end + +-- test normal insert/remove operations +local function test (a) + assert(not pcall(table.insert, a, 2, 20)); + table.insert(a, 10); + table.insert(a, 2, 20); + table.insert(a, 1, -1); + table.insert(a, 40); + table.insert(a, #a+1, 50) + table.insert(a, 2, -2) + assert(a[2] ~= undef) + assert(a["2"] == undef) + assert(not pcall(table.insert, a, 0, 20)); + assert(not pcall(table.insert, a, #a + 2, 20)); + assert(table.remove(a,1) == -1) + assert(table.remove(a,1) == -2) + assert(table.remove(a,1) == 10) + assert(table.remove(a,1) == 20) + assert(table.remove(a,1) == 40) + assert(table.remove(a,1) == 50) + assert(table.remove(a,1) == nil) + assert(table.remove(a) == nil) + assert(table.remove(a, #a) == nil) +end + +a = {n=0, [-7] = "ban"} +test(a) +assert(a.n == 0 and a[-7] == "ban") + +if not errors then + print("All tests passed!") +end