diff --git a/doc/ext_buffer.html b/doc/ext_buffer.html index 2443fc90..63c2efe3 100644 --- a/doc/ext_buffer.html +++ b/doc/ext_buffer.html @@ -127,7 +127,7 @@ space.
Buffers operate like a FIFO (first-in first-out) data structure. Data can be appended (written) to the end of the buffer and consumed (read) -from the front of the buffer. These operations can be freely mixed. +from the front of the buffer. These operations may be freely mixed.
The buffer space that holds the characters is managed automatically @@ -199,7 +199,7 @@ may be reused.
The buffer space of the buffer object is freed. The object itself -remains intact, empty and it may be reused. +remains intact, empty and may be reused.
Note: you normally don't need to use this method. The garbage collector @@ -404,8 +404,8 @@ speed is mostly constrained by object creation cost.
The serializer handles most Lua types, common FFI number types and -nested structures. Functions, thread objects, other FFI cdata, full -userdata and associated metatables cannot be serialized (yet). +nested structures. Functions, thread objects, other FFI cdata and full +userdata cannot be serialized (yet).
The encoder serializes nested structures as trees. Multiple references @@ -461,21 +461,31 @@ commonly occur as table keys of objects you are serializing. These keys are compactly encoded as indexes during serialization. A well chosen dictionary saves space and improves serialization performance. +
-dict needs to be an array of strings, starting at index 1 and -without holes (no nil inbetween). The table is anchored in the -buffer object and internally modified into a two-way index (don't do -this yourself, just pass a plain array). The table must not be modified -after it has been passed to buffer.new(). +dict needs to be an array of strings and metatable needs +to be an array of tables. Both starting at index 1 and without holes (no +nil inbetween). The tables are anchored in the buffer object and +internally modified into a two-way index (don't do this yourself, just pass +a plain array). The tables must not be modified after they have been passed +to buffer.new().
-The dict tables used by the encoder and decoder must be the -same. Put the most common entries at the front. Extend at the end to -ensure backwards-compatibility — older encodings can then still be -read. You may also set some indexes to false to explicitly drop -backwards-compatibility. Old encodings that use these indexes will throw -an error when decoded. +The dict and metatable tables used by the encoder and +decoder must be the same. Put the most common entries at the front. Extend +at the end to ensure backwards-compatibility — older encodings can +then still be read. You may also set some indexes to false to +explicitly drop backwards-compatibility. Old encodings that use these +indexes will throw an error when decoded. +
++Metatables that are not found in the metatable dictionary are +ignored when encoding. Decoding returns a table with a nil +metatable.
Note: parsing and preparation of the options table is somewhat @@ -564,7 +574,7 @@ suffix.
object → nil | false | true | null | lightud32 | lightud64 - | int | num | tab + | int | num | tab | tab_mt | int64 | uint64 | complex | string @@ -585,13 +595,14 @@ tab → 0x08 // Empty table | 0x0b a.U a*object h.U h*{object object} // Mixed | 0x0c a.U (a-1)*object // 1-based array | 0x0d a.U (a-1)*object h.U h*{object object} // Mixed +tab_mt → 0x0e (index-1).U tab // Metatable dict entry int64 → 0x10 int.L // FFI int64_t uint64 → 0x11 uint.L // FFI uint64_t complex → 0x12 re.L im.L // FFI complex string → (0x20+len).U len*char.B - | 0x0f (index-1).U // Dict entry + | 0x0f (index-1).U // String dict entry .B = 8 bit .I = 32 bit little-endian diff --git a/src/lib_buffer.c b/src/lib_buffer.c index ae065759..2e364861 100644 --- a/src/lib_buffer.c +++ b/src/lib_buffer.c @@ -288,7 +288,7 @@ LJLIB_CF(buffer_new) { MSize sz = 0; int targ = 1; - GCtab *env, *dict = NULL; + GCtab *env, *dict_str = NULL, *dict_mt = NULL; GCudata *ud; SBufExt *sbx; if (L->base < L->top && !tvistab(L->base)) { @@ -298,10 +298,16 @@ LJLIB_CF(buffer_new) } if (L->base+targ-1 < L->top) { GCtab *options = lj_lib_checktab(L, targ); - cTValue *opt_dict = lj_tab_getstr(options, lj_str_newlit(L, "dict")); + cTValue *opt_dict, *opt_mt; + opt_dict = lj_tab_getstr(options, lj_str_newlit(L, "dict")); if (opt_dict && tvistab(opt_dict)) { - dict = tabV(opt_dict); - lj_serialize_dict_prep(L, dict); + dict_str = tabV(opt_dict); + lj_serialize_dict_prep_str(L, dict_str); + } + opt_mt = lj_tab_getstr(options, lj_str_newlit(L, "metatable")); + if (opt_mt && tvistab(opt_mt)) { + dict_mt = tabV(opt_mt); + lj_serialize_dict_prep_mt(L, dict_mt); } } env = tabref(curr_func(L)->c.env); @@ -312,7 +318,8 @@ LJLIB_CF(buffer_new) setudataV(L, L->top++, ud); sbx = (SBufExt *)uddata(ud); lj_bufx_init(L, sbx); - setgcref(sbx->dict, obj2gco(dict)); + setgcref(sbx->dict_str, obj2gco(dict_str)); + setgcref(sbx->dict_mt, obj2gco(dict_mt)); if (sz > 0) lj_buf_need2((SBuf *)sbx, sz); return 1; } diff --git a/src/lj_buf.h b/src/lj_buf.h index 4ace2685..e2ac922e 100644 --- a/src/lj_buf.h +++ b/src/lj_buf.h @@ -27,7 +27,8 @@ typedef struct SBufExt { MRef bsb; /* Borrowed string buffer. */ }; char *r; /* Read pointer. */ - GCRef dict; /* Serialization string dictionary table. */ + GCRef dict_str; /* Serialization string dictionary table. */ + GCRef dict_mt; /* Serialization metatable dictionary table. */ int depth; /* Remaining recursion depth. */ } SBufExt; diff --git a/src/lj_gc.c b/src/lj_gc.c index 646a27b2..5a238542 100644 --- a/src/lj_gc.c +++ b/src/lj_gc.c @@ -69,8 +69,10 @@ static void gc_mark(global_State *g, GCobj *o) SBufExt *sbx = (SBufExt *)uddata(gco2ud(o)); if (sbufiscow(sbx) && gcref(sbx->cowref)) gc_markobj(g, gcref(sbx->cowref)); - if (gcref(sbx->dict)) - gc_markobj(g, gcref(sbx->dict)); + if (gcref(sbx->dict_str)) + gc_markobj(g, gcref(sbx->dict_str)); + if (gcref(sbx->dict_mt)) + gc_markobj(g, gcref(sbx->dict_mt)); } } else if (LJ_UNLIKELY(gct == ~LJ_TUPVAL)) { GCupval *uv = gco2uv(o); diff --git a/src/lj_serialize.c b/src/lj_serialize.c index 70ff4796..e12e3668 100644 --- a/src/lj_serialize.c +++ b/src/lj_serialize.c @@ -34,8 +34,8 @@ enum { SER_TAG_INT, SER_TAG_NUM, SER_TAG_TAB, /* 0x08 */ - SER_TAG_0x0e = SER_TAG_TAB+6, - SER_TAG_DICT, + SER_TAG_DICT_MT = SER_TAG_TAB+6, + SER_TAG_DICT_STR, SER_TAG_INT64, /* 0x10 */ SER_TAG_UINT64, SER_TAG_COMPLEX, @@ -124,7 +124,7 @@ static LJ_AINLINE char *serialize_ru124(char *r, char *w, uint32_t *pv) } /* Prepare string dictionary for use (once). */ -void LJ_FASTCALL lj_serialize_dict_prep(lua_State *L, GCtab *dict) +void LJ_FASTCALL lj_serialize_dict_prep_str(lua_State *L, GCtab *dict) { if (!dict->hmask) { /* No hash part means not prepared, yet. */ MSize i, len = lj_tab_len(dict); @@ -143,6 +143,26 @@ void LJ_FASTCALL lj_serialize_dict_prep(lua_State *L, GCtab *dict) } } +/* Prepare metatable dictionary for use (once). */ +void LJ_FASTCALL lj_serialize_dict_prep_mt(lua_State *L, GCtab *dict) +{ + if (!dict->hmask) { /* No hash part means not prepared, yet. */ + MSize i, len = lj_tab_len(dict); + if (!len) return; + lj_tab_resize(L, dict, dict->asize, hsize2hbits(len)); + for (i = 1; i <= len && i < dict->asize; i++) { + cTValue *o = arrayslot(dict, i); + if (tvistab(o)) { + if (tvisnil(lj_tab_get(L, dict, o))) { /* Ignore dups. */ + lj_tab_newkey(L, dict, o)->u64 = (uint64_t)(i-1); + } + } else if (!tvisfalse(o)) { + lj_err_caller(L, LJ_ERR_BUFFER_BADOPT); + } + } + } +} + /* -- Internal serializer ------------------------------------------------- */ /* Put serialized object into buffer. */ @@ -185,6 +205,22 @@ static char *serialize_put(char *w, SBufExt *sbx, cTValue *o) for (i = 0; i <= hmask; i++) nhash += !tvisnil(&node[i].val); } + /* Write metatable index. */ + if (LJ_UNLIKELY(tabref(sbx->dict_mt)) && tabref(t->metatable)) { + TValue mto; + Node *n; + settabV(sbufL(sbx), &mto, tabref(t->metatable)); + n = hashgcref(tabref(sbx->dict_mt), mto.gcr); + do { + if (n->key.u64 == mto.u64) { + uint32_t idx = n->val.u32.lo; + w = serialize_more(w, sbx, 1+5); + *w++ = SER_TAG_DICT_MT; + w = serialize_wu124(w, idx); + break; + } + } while ((n = nextnode(n))); + } /* Write number of array slots and hash slots. */ w = serialize_more(w, sbx, 1+2*5); *w++ = (char)(SER_TAG_TAB + (nhash ? 1 : 0) + (narray ? one : 0)); @@ -197,19 +233,19 @@ static char *serialize_put(char *w, SBufExt *sbx, cTValue *o) } if (nhash) { /* Write hash entries. */ const Node *node = noderef(t->node) + t->hmask; - GCtab *dict = tabref(sbx->dict); - if (LJ_UNLIKELY(dict)) { + GCtab *dict_str = tabref(sbx->dict_str); + if (LJ_UNLIKELY(dict_str)) { for (;; node--) if (!tvisnil(&node->val)) { if (LJ_LIKELY(tvisstr(&node->key))) { /* Inlined lj_tab_getstr is 30% faster. */ const GCstr *str = strV(&node->key); - Node *n = hashstr(dict, str); + Node *n = hashstr(dict_str, str); do { if (tvisstr(&n->key) && strV(&n->key) == str) { uint32_t idx = n->val.u32.lo; w = serialize_more(w, sbx, 1+5); - *w++ = SER_TAG_DICT; + *w++ = SER_TAG_DICT_STR; w = serialize_wu124(w, idx); break; } @@ -322,19 +358,32 @@ static char *serialize_get(char *r, SBufExt *sbx, TValue *o) if (!tvisnum(o)) setnanV(o); } else if (tp <= SER_TAG_TRUE) { setpriV(o, ~tp); - } else if (tp == SER_TAG_DICT) { - GCtab *dict; + } else if (tp == SER_TAG_DICT_STR) { + GCtab *dict_str; uint32_t idx; r = serialize_ru124(r, w, &idx); idx++; - dict = tabref(sbx->dict); - if (dict && idx < dict->asize && tvisstr(arrayslot(dict, idx))) - copyTV(sbufL(sbx), o, arrayslot(dict, idx)); + dict_str = tabref(sbx->dict_str); + if (dict_str && idx < dict_str->asize && tvisstr(arrayslot(dict_str, idx))) + copyTV(sbufL(sbx), o, arrayslot(dict_str, idx)); else lj_err_callerv(sbufL(sbx), LJ_ERR_BUFFER_BADDICTX, idx); - } else if (tp >= SER_TAG_TAB && tp < SER_TAG_TAB+6) { + } else if (tp >= SER_TAG_TAB && tp <= SER_TAG_DICT_MT) { uint32_t narray = 0, nhash = 0; - GCtab *t; + GCtab *t, *mt = NULL; + if (tp == SER_TAG_DICT_MT) { + GCtab *dict_mt; + uint32_t idx; + r = serialize_ru124(r, w, &idx); if (LJ_UNLIKELY(!r)) goto eob; + idx++; + dict_mt = tabref(sbx->dict_mt); + if (dict_mt && idx < dict_mt->asize && tvistab(arrayslot(dict_mt, idx))) + mt = tabV(arrayslot(dict_mt, idx)); + else + lj_err_callerv(sbufL(sbx), LJ_ERR_BUFFER_BADDICTX, idx); + r = serialize_ru124(r, w, &tp); if (LJ_UNLIKELY(!r)) goto eob; + if (!(tp >= SER_TAG_TAB && tp < SER_TAG_DICT_MT)) goto badtag; + } if (tp >= SER_TAG_TAB+2) { r = serialize_ru124(r, w, &narray); if (LJ_UNLIKELY(!r)) goto eob; } @@ -342,6 +391,8 @@ static char *serialize_get(char *r, SBufExt *sbx, TValue *o) r = serialize_ru124(r, w, &nhash); if (LJ_UNLIKELY(!r)) goto eob; } t = lj_tab_new(sbufL(sbx), narray, hsize2hbits(nhash)); + /* NOBARRIER: The table is new (marked white). */ + setgcref(t->metatable, obj2gco(mt)); settabV(sbufL(sbx), o, t); if (narray) { TValue *oa = tvref(t->array) + (tp >= SER_TAG_TAB+4); @@ -395,6 +446,7 @@ static char *serialize_get(char *r, SBufExt *sbx, TValue *o) setrawlightudV(o, (void *)ud); #endif } else { +badtag: lj_err_callerv(sbufL(sbx), LJ_ERR_BUFFER_BADDEC, tp); } return r; @@ -460,10 +512,11 @@ LJ_FUNC MSize LJ_FASTCALL lj_serialize_peektype(SBufExt *sbx) case SER_TAG_NUM: return IRT_NUM; case SER_TAG_TAB: case SER_TAG_TAB+1: case SER_TAG_TAB+2: case SER_TAG_TAB+3: case SER_TAG_TAB+4: case SER_TAG_TAB+5: + case SER_TAG_DICT_MT: return IRT_TAB; case SER_TAG_INT64: case SER_TAG_UINT64: case SER_TAG_COMPLEX: return IRT_CDATA; - case SER_TAG_DICT: + case SER_TAG_DICT_STR: default: return IRT_STR; } diff --git a/src/lj_serialize.h b/src/lj_serialize.h index 9bd780ca..1fda23eb 100644 --- a/src/lj_serialize.h +++ b/src/lj_serialize.h @@ -13,7 +13,8 @@ #define LJ_SERIALIZE_DEPTH 100 /* Default depth. */ -LJ_FUNC void LJ_FASTCALL lj_serialize_dict_prep(lua_State *L, GCtab *dict); +LJ_FUNC void LJ_FASTCALL lj_serialize_dict_prep_str(lua_State *L, GCtab *dict); +LJ_FUNC void LJ_FASTCALL lj_serialize_dict_prep_mt(lua_State *L, GCtab *dict); LJ_FUNC SBufExt * LJ_FASTCALL lj_serialize_put(SBufExt *sbx, cTValue *o); LJ_FUNC char * LJ_FASTCALL lj_serialize_get(SBufExt *sbx, TValue *o); LJ_FUNC GCstr * LJ_FASTCALL lj_serialize_encode(lua_State *L, cTValue *o);